奇袭:分治,桶

为君一笑 提交于 2020-02-11 01:08:43

大致的简化题意在我的考试反思里有,裸n2及以上的暴力也不打算再讲了。

 1 #include<cstdio>
 2 #include<ctime>
 3 using namespace std;
 4 #define rus register unsigned short
 5 inline unsigned short max(const rus a,const rus b){return a>b?a:b;}
 6 inline unsigned short min(const rus a,const rus b){return a<b?a:b;}
 7 inline unsigned short read(){
 8     rus a=0;register char ch=getchar();
 9     while(ch<48||ch>57)ch=getchar();
10     while(ch>=48&&ch<=57)a=(a<<3)+(a<<1)+ch-48,ch=getchar();
11     return a;
12 }
13 int ans;
14 unsigned short pos[50005],n;
15 int main(){
16     n=read();
17     for(rus i=1,x,y;i<=n;++i) x=read(),y=read(), pos[x]=y;
18     for(rus i=1,maxx=0,minn=50006;i<=n;++i,maxx=0,minn=50006){
19         if(clock()>990000){printf("%d\n",ans+n-i+1);return 0;}
20         for(rus j=i;j<=n;++j){
21             maxx=max(maxx,pos[j]); minn=min(minn,pos[j]);
22             if(maxx-minn==j-i)ans++;
23         }
24     }
25     
26     printf("%d\n",ans);//printf("%ld\n",clock());
27 }
考场上我的不要脸的n2卡常

跳跃的91分n2还是要讲一下的:

看上边的代码里的枚举,我们在循环中不断更新maxx和minn。

然而我们需要的是maxx-minn==j-i,如果maxx和minn的差值很大,那么j接下来的很多次枚举都不会更新答案。

我们考虑跳过这段区间直到j=maxx-minn+i(简单移项别说不会),这才有可能更新答案嘛。

这时j跳了一大段,中间的maxx和minn怎么更新呢?

我们考虑预处理出区间最大最小值,RMQ(ST,线段树,树状数组选你喜欢的就好)

这样我们就可以跳起来了!枚举量减小了很多。

但是,答案的累加还是ans++的,而本题最大的答案是50000×50000的正方形里的主对角线。

这时的答案是50000×50001/2>1e9,单纯ans++这个思路就会TLE

所以让我们放弃它,从头想。

码农与一个厉害的程序员的区别在哪里?一个只会码,另一个会思考。

我们对式子动一下手脚吧,假装我们不是码农的样子。

看数据范围,n log是可以接受的。考虑那些含有二分思想的玩意。

有的题解说线段树,其实是类似一个动态权值线段树的玩意,思想大同小异。

一个区间,从中间mid分成两个区间,如果最小值在左边最大值在右边。。。

用maxr表示max(a[mid+1],a[mid+2],...,a[r]),   minl=min(a[mid],a[mid-1],...,a[l])

那么式子是maxr-minl=r-l。稍微移项,maxr-r=minl-l,现在左右两边无关了。

对于每一个r,把maxr-r的桶++,对应左边的minl-l把桶里面的值ans+=桶值。

可能有人不理解桶是什么东西吧?天天爱跑步做了吗?

没做也没关系。

大致思路就是,如果我们需要单点查询/修改某一个值出现了多少次,怎么办?

很简单那,开一个数组,数组的对应位数++,查询也直接查数组的那一位就好了呀。

这种存权值(少数时候会存权值区间)的数组就被叫做桶。

然而这乍一下维护桶是错的,为什么呢?

因为在左边的l从mid向区间左端L枚举的过程中,并非所有的r都还能满足我们预设的“最小值在左边,最大值在右边”的条件

但是我们仍然能发现,对于每一个左端点l,它所对应的合法的右区间r总是连续的,递增的。

l继续向左移--,对应的右区间的两个端点称之为rl和rr(是闭区间[rl,rr]),它们一定不会向左移。

证明:上一步rl~rr是最大的合法决策区间,那么l左移后,maxl可能大了,minl可能小了,也可能都不变。

如果maxl变大了,那么maxr还需要满足大于maxl的话,可能需要把rl右移,它所贡献的桶值不再有效。

while(rl<=R&&maxx[l]>maxx[rl])t[maxx[rl]-rl]--,rl++;

同理,如果minl变小了,那么能够满足minr>minl的右区间可能会扩大,扩建新桶。

while(rr+1<=R&&minn[l]<minn[rr+1])rr++,t[maxx[rr]-rr];

这样就可以累加l对应的答案了。但是我们可悲的发现答案还是不对,为什么呢?

因为我们并没有限制rl和rr的大小关系,而实际上可能会出现rl>rr的情况,导致桶中有负值。

有很多解决办法:控制rl与rr的大小关系,或累加答案时判断大小关系,或累加答案是判断桶的正负。

当所有左区间枚举完毕后,记得清空你的maxx和minn以及t(桶)数组。

不要memset啊同志们,nlog次O(n)的memset想什么呢!

可以改一下memset的参数让它清区间,或手动for清,都可以。

这样我们已经考虑了最小值在左最大值在右的情况,接下来说最值都在左侧的情况。

再翻回来找出我们的算式:max-min=r-l,移项l+max-min=r

那么就很简单了,对于每个l,设rrr=l+maxl-minl,如果rrr满足设定的条件就好了

if(rrr>=mid+1&&rrr<=R&&maxx[rrr]<maxx[l]&&minn[rrr]>minn[l]) ans++;

现在我们已经处理了极值都在左边的情况和左小右大的情况。

还剩都在右边和左大右小。这两种情况嘛。。。我们尝试把数组翻过来,这两种情况就变成了上面的那两种情况。

reverse。但注意要更改一下mid值。如原区间[1,5],reverse前是[1,3]和[4,5]

翻一下之后应该是[5,4]和[3,1],也就是左边应该有两个元素,mid=2而不在是原来的3。

没了,递归二分,到l==r直接ans++,return。没了。

顺便提一嘴,桶里那个minl-l之类的可能负过去,你可以学习下tdcp和mikufun的数组负下标操作。

否则记得给它加上50000。

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<algorithm>
 4 #include<cstring>
 5 using namespace std;
 6 int ans,n,a[50005],maxx[50005],minn[50005],t[200005];
 7 void ask(const int l,const int r){
 8     if(l==r){ans++;/*printf("!!!---%d %d---!!!\n",l,ans);*/return;}
 9     register int mid=l+r>>1;
10     ask(l,mid);ask(mid+1,r);//printf("-----%d %d-----\n",l,r);
11     for(int p=0;p<=1;++p){//for(int i=1;i<=n;++i)printf("%d ",a[i]);puts("");
12         for(int i=mid;i>=l;--i) minn[i]=min(minn[i+1],a[i]),maxx[i]=max(maxx[i+1],a[i]);
13         minn[mid+1]=maxx[mid+1]=a[mid+1];
14         for(int i=mid+2;i<=r;++i) minn[i]=min(minn[i-1],a[i]),maxx[i]=max(maxx[i-1],a[i]);
15         for(int i=mid,rl=mid+1,rr=mid;i>=l;--i){//printf("---%d---\n",i);
16             while(rl<=r&&maxx[rl]<maxx[i]) t[maxx[rl]-rl+100000]--,rl++;
17             while(rr+1<=r&&minn[rr+1]>minn[i]) rr++,t[maxx[rr]-rr+100000]++;
18             if(rl<=rr&&t[minn[i]-i+100000]) ans+=t[minn[i]-i+100000];//;printf("%d %d\n",rl,rr);for(int ii=100000-3;ii<=100000+3;++ii)printf("%d ",t[ii]);puts("");
19             if(i==l) while(rl<=rr)t[maxx[rl]-rl+100000]--,rl++;
20             if(i==l) while(rr<rl-1) rr++,t[maxx[rr]-rr+100000]++;
21             //for(int ii=100000-3;ii<=100000+3;++ii)printf("%d ",t[ii]);puts("");
22         }//printf("ans=%d\n",ans);
23         for(int i=mid,rrr=i+maxx[i]-minn[i];i>=l;--i,rrr=i+maxx[i]-minn[i]) 
24             if(rrr<=r&&rrr>=mid+1&&maxx[rrr]<maxx[i]&&minn[rrr]>minn[i]) ans++;
25         for(int i=r;i>=l;--i)minn[i]=0x3fffffff,maxx[i]=0;
26         reverse(a+l,a+r+1);mid=r-mid+l-1;//printf("ans=%d\n",ans);
27     }
28 }
29 int main(){memset(minn,0x3f,sizeof(minn));
30     scanf("%d",&n);for(int i=1,x,y;i<=n;++i)scanf("%d%d",&x,&y),a[x]=y;
31     ask(1,n);
32     printf("%d\n",ans);
33 }
预警:32行
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!