HDU 5289 Assignment
单调队列、线段树
题目
给一个数列,求满足下列条件的子区间的个数:
对于子区间[L, R]内的任意两个元素的差值小于k。
思路
暴力
O(nlognlogn)
(967ms):
两个set维护,双指针ij。i指向区间首端元素,j指向区间尾端元素。set统计最值。如果最大值减最小值大于k了,那么在set中吧j指向的元素移除,j++。
单调栈
O(n)
(358ms):
类似暴力,双指针,不过吧set优化成单调栈。单调栈ma维护单调递减的最大值,mi维护单调递增的最小值。这样栈里具有插入顺序与单调的两个性质。当最大值减最小值大于k了,判断栈头元素等不等于num[j],等于就移除首元素,然后增j。为了避免相等元素的情况,栈要严格增(减)。
每次处理完统计+=i-j。
线段树
O(nlogn)
(421ms):
线段树做法比较耿直,枚举左端点l,查询右端点l(左端点右侧第一个大于k+num[l]或小于k+num[l]的r)。但是线段树的query不太好想。大体就是,用-1表示没结果,查询时遇到没结果或者有更优解(ans>l)时更新ans。查询时先查左儿子,如果有结果就不需要查右儿子了,因为左儿子的肯定更靠左。
int ans;
void queryR(int L, int R, LL num, int l, int r, int rt, LL k)
{
if(l==r)
{
if(abs(stree[rt].ma-num)>=k||abs(stree[rt].mi-num)>=k)
{
if(ans==-1||ans>l)
ans=l;
}
return;
}
int mid=(l+r)>>1;
if(L<=mid)
{
if(abs(stree[rt<<1].mi-num)>=k||abs(stree[rt<<1].ma-num)>=k)
queryR(L, R, num, lson, k);
}
if(ans==-1&&mid<R)
{
if(abs(stree[rt<<1|1].mi-num)>=k||abs(stree[rt<<1|1].ma-num)>=k)
queryR(L, R, num, rson, k);
}
}
另外,注意ans不可能大于上一次的ans,要记得取小。
还有类似线段树的RMQ做法,也是 O(nlogn) 。
代码
暴力 O(nlognlogn) (967ms):
#include <cstdio>
#include <set>
using namespace std;
typedef long long ll;
multiset<int> mi;
multiset<int,greater<int> > ma;
int a[100005];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,k;
scanf("%d%d",&n,&k);
ma.clear();
mi.clear();
ll ans = 0;
int i,j = 0;
multiset<int>::iterator it1,it2;
for(i = 0; i < n; i++){
scanf("%d",&a[i]);
ma.insert(a[i]);
mi.insert(a[i]);
while(j <= i && *ma.begin()-*mi.begin() >= k){
it1 = ma.find(a[j]);
it2 = mi.find(a[j]);
ma.erase(it1);
mi.erase(it2);
j++;
}
ans += ma.size();
}
printf("%lld\n",ans);
}
return 0;
}
单调栈 O(n) (358ms):
#include<bits/stdc++.h>
#define M(a,b) memset(a,b,sizeof(a))
typedef long long LL;
using namespace std;
const int MAXN=100007;
deque<LL> ma, mi;
LL num[MAXN];
int main()
{
int T;scanf("%d", &T);
while(T--)
{
ma.clear(), mi.clear();
int m, n;scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
{
scanf("%lld", &num[i]);
}
int j=1;LL ans=0;
for(int i=1;i<=n;i++)
{
while(!mi.empty()&&mi.back()>num[i]) mi.pop_back();
mi.push_back(num[i]);
while(!ma.empty()&&ma.back()<num[i]) ma.pop_back();
ma.push_back(num[i]);
while(!ma.empty()&&!mi.empty()&&ma.front()-mi.front()>=m)
{
ans+=(i-j);
if(ma.front()==num[j]) ma.pop_front();
if(mi.front()==num[j]) mi.pop_front();
j++;
}
}
while(j<=n) ans+=LL(n-j+1),j++;
printf("%lld\n", ans);
}
return 0;
}
线段树 O(nlogn) (421ms):
#include<bits/stdc++.h>
#define M(a,b) memset(a,b,sizeof(a))
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
typedef long long LL;
using namespace std;
const int MAXN=100007;
const int oo=0x3f3f3f3f;
LL num[MAXN];
struct Stree
{
LL ma, mi;
}stree[MAXN<<2];
void pushup(int rt)
{
stree[rt].ma=max(stree[rt<<1].ma, stree[rt<<1|1].ma);
stree[rt].mi=min(stree[rt<<1].mi, stree[rt<<1|1].mi);
}
void build(int l, int r, int rt)
{
if(l==r)
{
stree[rt].ma=stree[rt].mi=num[l];
return;
}
int mid=(l+r)>>1;
build(lson), build(rson);
pushup(rt);
}
int ans;
void queryR(int L, int R, LL num, int l, int r, int rt, LL k)
{
if(l==r)
{
if(abs(stree[rt].ma-num)>=k||abs(stree[rt].mi-num)>=k)
{
if(ans==-1||ans>l)
ans=l;
}
return;
}
int mid=(l+r)>>1;
if(L<=mid)
{
if(abs(stree[rt<<1].mi-num)>=k||abs(stree[rt<<1].ma-num)>=k)
queryR(L, R, num, lson, k);
}
if(ans==-1&&mid<R)
{
if(abs(stree[rt<<1|1].mi-num)>=k||abs(stree[rt<<1|1].ma-num)>=k)
queryR(L, R, num, rson, k);
}
}
int main()
{
int T;scanf("%d", &T);
while(T--)
{
int m, n;scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
scanf("%lld", &num[i]);
build(1, n, 1);
LL res=1;
int last=n;
for(int i=n-1;i>=1;i--)
{
ans=-1;
queryR(i, n, num[i], 1, n, 1, m);
if(ans==-1)
ans=n;
else
ans--;
ans=min(ans, last);
last=ans;
res+=(ans-i+1);
}
printf("%lld\n", res);
}
return 0;
}