[考试反思]0206省选模拟19:混沌
肉眼可见的炸了。这个题目难度简直了。。。
关键是暴力分还少,每题就$5$分那样子。于是就没怎么写而在尝试想正解。
以为是三个数据结构,被恶心到了,然后就啥都没想出来。
其实T1差不多已经想到了,但当时时间也不多了就弃了。
结果T1部分分卡精T2部分分不会T3部分分卡常。
就靠着T3的一个签到分+扩展后缀自动机的10分部分分拿了这点分。
考后改题非常不顺畅。。
T1还是比较简单的,在中午就改完了。
然后是恶心的T2,研究了好半天,去问skyh,他说22分的部分分做法比较关键。
于是听他讲了一段时间然后开始写,调啊调,晚饭左右才把22分的部分分写完。
接下来按照自己的理解尝试把它改成正解,因为一点都没听懂$LNC$讲题时在讲什么所以自己写的时候遇到了好多问题。
最后终于写出来了,结果复杂度还伪了,TLE36,怀疑人生。
后来一个实现细节一共有两种方法,我觉得skyh写的是比较麻烦的那一种所以又去问mikufun/Mouding。
然后开始写,越写细节越多,后来发现是前一半用skyh的方法后一半用mikufun他们的方法会格外麻烦。。。
以后听人讲题一定要从头听到尾!!!不要每个思路沾一半!!!
最后一直改到晚上23:45左右,终于又一次过了样例,交上去就$WA0$
气急败坏,因为第二天还要考试所以去睡觉了。然后连着这反思和改题一起鸽到了第二天。
至少这天的努力没有白费。。。第二天改一改也就改出来了。。。是个加减号写反了,状态不好居然没看出来。。。
T1:鱼死网破(clash)
大意:第一象限有$n$点和$k$条平行与x轴的线段,在线的从第四象限选点进行$m$次询问:与多少个第一象限的点连线与已有线段不相交。$n,m \le 10^5,k \le 50$
运用差分的思想,可以发现,由第一象限的点向线段端点连线并延长,左侧线+1,右侧线-1。
这样的话,第四象限的点的答案就是所有在其左侧的射线的权值和。
但是有一点问题,两点之间可能有多于1条线段,这样会使差分统计错误。
为此我们对于每个第一象限的点预处理它与所有线段发出的射线,极角有交集的线段合并为1个即可。
然后把所有射线存到对应线段端点的$vector$中,每次查询时在所有$2k$个线段端点的$vector$中按照极角二分得到答案。
运用叉积判断方向与极角,没有精度问题。虽说$double$好像也可以过。
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct A{ 4 int y,x; 5 friend bool operator<(A a,A b){return 1ll*a.x*b.y-1ll*a.y*b.x>0;} 6 };vector<A>v[111]; 7 struct P{A a;int o;friend bool operator<(P a,P b){return a.a<b.a;}}p[111]; 8 int n,m,k,op,la,x[111111],y[111111],l[66],r[66],h[66],ans,rx[111]; 9 int main(){ 10 scanf("%d%d%d%d",&n,&k,&m,&op); 11 for(int i=1;i<=n;++i)scanf("%d%d",&x[i],&y[i]); 12 for(int i=1;i<=k;++i)scanf("%d%d%d",&l[i],&r[i],&h[i]),rx[i<<1]=l[i],rx[i<<1|1]=r[i]; 13 for(int i=1;i<=n;++i){ 14 int pc=0,c=0; 15 for(int j=1;j<=k;++j)if(y[i]>h[j])p[++pc]=(P){(A){y[i]-h[j],x[i]-l[j]},j<<1},p[++pc]=(P){(A){y[i]-h[j],x[i]-r[j]},j<<1|1}; 16 sort(p+1,p+1+pc); 17 for(int j=1;c+=p[j].o&1?-1:1,j<=pc;++j) 18 if(p[j].o&1&&!c)v[p[j].o].push_back(p[j].a); 19 else if(!(p[j].o&1)&&c==1)v[p[j].o].push_back(p[j].a); 20 } 21 for(int i=2;i<k+1<<1;++i)sort(v[i].begin(),v[i].end()); 22 while(m-->0){ 23 int X,Y;scanf("%d%d",&X,&Y);ans*=op;X^=ans;Y^=ans;ans=n; 24 for(int i=2;i<k+1<<1;i+=2)ans-=upper_bound(v[i].begin(),v[i].end(),(A){h[i>>1]-Y,rx[i]-X})-v[i].begin(); 25 for(int i=3;i<k+1<<1;i+=2)ans+=lower_bound(v[i].begin(),v[i].end(),(A){h[i>>1]-Y,rx[i]-X})-v[i].begin(); 26 printf("%d\n",ans); 27 } 28 }
T2:漏网之鱼(escape)
大意:给定序列,求区间的所有子区间的$mex$和。$n,Q \le 10^6$
关键的一档$22$分部分分:$Q=1,l=1,r=n$
考虑全部子区间的贡献,这个很麻烦,我们枚举子区间的右端点$r$来依次统计贡献。
拿一棵线段树,下标$i$的是$[i,r]$这个区间的$mex$值。很明显它是单调不增的。维护区间和就是当前右端点的答案。
考虑右端点左移会发生什么,设$x$上次出现位置是$last_x$。那么线段树上的变化就是$[last_{A_r}+1,r)$这段位置的$mex$值对$A_r$取$min$。
所以要维护的操作是区间求和和区间取$min$。然而发现不用什么特殊性质的话这个信息并不能在合法复杂度内维护。
然而取$min$这并没有影响单调性这个好性质。所以我们维护一下区间最值,在线段树上二分,即可转变为区间赋值。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 4000005 4 int id,n,q,a[S],mn[S],mex[S],pmn[S],lzc[S],mx[S];long long tot[S],ans; 5 vector<int>v[S]; 6 #define lc p<<1 7 #define rc p<<1|1 8 #define md (cl+cr>>1) 9 void build(int p,int cl,int cr){ 10 lzc[p]=-1; 11 if(cl==cr){mn[p]=mx[p]=tot[p]=mex[cl];return;} 12 build(lc,cl,md);build(rc,md+1,cr); 13 tot[p]=tot[lc]+tot[rc];mn[p]=min(mn[lc],mn[rc]);mx[p]=max(mx[lc],mx[rc]); 14 } 15 void chg(int l,int r,int v,int p=1,int cl=1,int cr=n){ 16 if(mn[p]>=v&&l<=cl&&cr<=r){mn[p]=mx[p]=lzc[p]=v;tot[p]=(cr-cl+1ll)*v;return;} 17 if(cl==cr)return;if(mx[p]<=v)return; 18 if(lzc[p]!=-1)mn[lc]=mx[lc]=mx[rc]=mn[rc]=lzc[lc]=lzc[rc]=lzc[p],tot[lc]=(md-cl+1ll)*lzc[p],tot[rc]=(cr-md+0ll)*lzc[p],lzc[p]=-1; 19 if(l<=md)chg(l,r,v,lc,cl,md); 20 if(r>md)chg(l,r,v,rc,md+1,cr); 21 tot[p]=tot[lc]+tot[rc];mn[p]=min(mn[lc],mn[rc]);mx[p]=max(mx[lc],mx[rc]); 22 } 23 int main(){//freopen("1.in","r",stdin); 24 cin>>id>>n; 25 for(int i=1;i<=n;++i)scanf("%d",&a[i]),v[i].push_back(0);v[0].push_back(0); 26 for(int i=1;i<=n;++i)if(a[i]<S)v[a[i]].push_back(i); 27 pmn[0]=v[0][v[0].size()-1]; 28 for(int i=1;v[i].size()>1;++i)pmn[i]=min(pmn[i-1],v[i][v[i].size()-1]),mex[1]=i+1; 29 for(int i=2;i<=n;++i){ 30 mex[i]=mex[i-1]; 31 while(mex[i]&&pmn[mex[i]-1]<i)mex[i]--; 32 }build(1,1,n);ans=tot[1]; 33 for(int i=n-1;i;--i){ 34 if(a[i+1]<=n)v[a[i+1]].pop_back(),chg(*(--v[a[i+1]].end())+1,i,a[i+1]); 35 chg(i+1,i+1,0);ans+=tot[1]; 36 }cout<<ans<<endl; 37 }
没有特殊性质的询问如何处理?
对于$[l,r]$这个询问,我们应该在右端点为$i \in [l,r]$时查询区间$[l,r]$的值并求和。
然而这复杂度肯定是不可行的,第二个“区间”的限制我们可以在线段树上直接查询,而第一个区间我们可以采取前缀和解决。
在右端点小于$l$时区间$[l,r]$的$mex$贡献都已经是零,所以我们只需要右端点$\ge 1$的总贡献减去右端点$>r$时的总贡献就是最终答案。
所以这棵线段树需要我们在使右端点左移的同时做前缀和。
维护一个全局变量$tim$表示时间(右端点)。然后在不加修改的情况下每个位置的贡献都是以$tim$为自变量的一次函数。
一次函数有可加性,可以直接用线段树维护。
所以我们要处理的就是区间赋值对这些一次函数的影响。
我们可以维护两个懒标记数组$lct,lzc$分别表示这个节点在右端点为几时就已经被修改,以及被修改成了几。
($lct$这个变量名好啊,$last \ changed \ time$)
但是这样的话懒标记不能直接覆盖,需要迭代下传,时间复杂度会退化成$O(n^2 log \ n)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 4000005 4 int id,n,Q,a[S],mn[S],mex[S],pmn[S],lzc[S],lct[S],mx[S],tim;long long A[S],B[S],ans[S]; 5 struct qs{int l,r,o;friend bool operator<(qs a,qs b){return a.r>b.r;}}q[S]; 6 vector<int>v[S]; 7 #define lc p<<1 8 #define rc p<<1|1 9 #define md (cl+cr>>1) 10 void up(int p){mn[p]=min(mn[lc],mn[rc]);mx[p]=max(mx[lc],mx[rc]);A[p]=A[lc]+A[rc];B[p]=B[lc]+B[rc];} 11 void down(int p,int cl,int cr){ 12 if(cl==cr)return; 13 if(~lzc[lc])down(lc,cl,md); 14 if(~lzc[rc])down(rc,md+1,cr); 15 mn[lc]=mx[lc]=mx[rc]=mn[rc]=lzc[lc]=lzc[rc]=lzc[p];lct[lc]=lct[rc]=lct[p];long long ra; 16 ra=A[lc];A[lc]=(cl-md-1ll)*lzc[p];B[lc]=ra*lct[p]+B[lc]-A[lc]*lct[p]; 17 ra=A[rc];A[rc]=(md-cr-0ll)*lzc[p];B[rc]=ra*lct[p]+B[rc]-A[rc]*lct[p];//if(rc==5)cout<<lzc[p]<<' '<<A[rc]<<'X'<<B[rc]<<endl; 18 lzc[p]=-1; 19 } 20 void build(int p,int cl,int cr){ 21 lzc[p]=-1; 22 if(cl==cr){mn[p]=mx[p]=mex[cl];A[p]=-mex[cl];B[p]=(n+1ll)*mex[cl];return;} 23 build(lc,cl,md);build(rc,md+1,cr);up(p); 24 } 25 void chg(int l,int r,int v,int p=1,int cl=1,int cr=n){ 26 if(~lzc[p])down(p,cl,cr); 27 if(mn[p]>=v&&l<=cl&&cr<=r){ 28 mn[p]=mx[p]=lzc[p]=v;lct[p]=tim;long long ra=A[p];//cout<<"lazy tag"<<cl<<' '<<cr<<' '<<v<<endl; 29 A[p]=(cl-cr-1ll)*v;B[p]=ra*tim+B[p]-A[p]*tim;//f(p==5)cout<<v<<' '<<A[p]<<'x'<<B[p]<<endl; 30 return; 31 } 32 if(cl==cr)return;if(mx[p]<=v)return; 33 if(l<=md)chg(l,r,v,lc,cl,md); if(r>md)chg(l,r,v,rc,md+1,cr); 34 up(p); 35 } 36 long long ask(int l,int r,int p=1,int cl=1,int cr=n){ 37 if(l<=cl&&cr<=r)return A[p]*tim+B[p]; 38 if(~lzc[p])down(p,cl,cr); 39 return (l<=md?ask(l,r,lc,cl,md):0)+(r>md?ask(l,r,rc,md+1,cr):0); 40 } 41 void modify(int i){//,,cout<<"mod"<<' '<<i<<endl; 42 if(a[i]<=n)v[a[i]].pop_back(),chg(*(--v[a[i]].end())+1,i-1,a[i]); 43 chg(i,i,0); 44 } 45 int main(){//freopen("1.in","r",stdin); 46 cin>>id>>n; 47 for(int i=1;i<=n;++i)scanf("%d",&a[i]),v[i].push_back(0);v[0].push_back(0); 48 for(int i=1;i<=n;++i)if(a[i]<S)v[a[i]].push_back(i); 49 pmn[0]=v[0][v[0].size()-1];mex[1]=1; 50 if(v[0].size()>1)for(int i=1;v[i].size()>1;++i)pmn[i]=min(pmn[i-1],v[i][v[i].size()-1]),mex[1]=i+1; 51 for(int i=2;i<=n;++i){ 52 mex[i]=mex[i-1]; 53 while(mex[i]&&pmn[mex[i]-1]<i)mex[i]--; 54 }build(1,1,n);tim=n;//,,cout<<"!!!"<<mex[2]<<endl; 55 cin>>Q;for(int i=1;i<=Q;++i)scanf("%d%d",&q[i].l,&q[i].r),q[i].o=i; 56 sort(q+1,q+1+Q); 57 for(int i=1;i<=Q;++i){ 58 while(tim>q[i].r+1)modify(tim),tim--; 59 ans[q[i].o]=q[i].r==n?0:-ask(q[i].l,q[i].r);//cout<<q[i].o<<' '<<ans[q[i].o]<<endl; 60 } 61 while(tim)modify(tim),tim--; 62 for(int i=1;i<=Q;++i)ans[q[i].o]+=ask(q[i].l,q[i].r); 63 for(int i=1;i<=Q;++i)printf("%lld\n",ans[i]); 64 }
换一种思路。我们维护的一次函数设为$Ax+B$
如果它在$tim$时刻被修改。(对应图中$BC,DE,GH$之间)。
$LM$之间对应右端点为0。如果在$IJ$对应的时刻查询,最后的贡献应该是$IJ$以左的黄色部分。
我们发现我们在修改时,我们可以先让原一次函数照样计算,再去掉多算的部分(也就是红蓝绿部分)。
而我们可以直接在修改时把整个绿色部分加入贡献(变化量乘上当前时间)。其他颜色同理。
这个可以用一个数组维护,直接在修改是就可以打懒标记。
然后多减的部分就是黑框里的红蓝绿三色,把它们加回来,就是当前时间乘以总变化量。这个可以在下传懒标记时直接计算。
然后就可以维护了。说着简单实际上细节是真的多。。。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 4000005 4 int id,n,Q,a[S],mn[S],mex[S],pmn[S],lzc[S],mx[S],tim;long long A[S],B[S],ans[S],rb[S]; 5 struct qs{int l,r,o;friend bool operator<(qs a,qs b){return a.r>b.r;}}q[S]; 6 vector<int>v[S]; 7 #define lc p<<1 8 #define rc p<<1|1 9 #define md (cl+cr>>1) 10 void up(int p){mn[p]=min(mn[lc],mn[rc]);mx[p]=max(mx[lc],mx[rc]);A[p]=A[lc]+A[rc];B[p]=B[lc]+B[rc];} 11 void down(int p,int cl,int cr){ 12 long long rr=mn[lc],ra;mn[lc]=mx[lc]=mx[rc]=mn[rc]=lzc[lc]=lzc[rc]=lzc[p]; 13 rb[lc]+=rb[p];ra=A[lc];A[lc]=(cl-md-1ll)*lzc[p];B[lc]=B[lc]+ra*tim+(md-cl+1ll)*((rr-mn[p]+0ll)*(tim-1)-rb[p])-A[lc]*tim; 14 rb[rc]+=rb[p];ra=A[rc];A[rc]=(md-cr-0ll)*lzc[p];B[rc]=B[rc]+ra*tim+(cr-md-0ll)*((rr-mn[p]+0ll)*(tim-1)-rb[p])-A[rc]*tim; 15 lzc[p]=-1;rb[p]=0; 16 } 17 void build(int p,int cl,int cr){ 18 lzc[p]=-1; 19 if(cl==cr){mn[p]=mx[p]=mex[cl];A[p]=-mex[cl];B[p]=(n+1ll)*mex[cl];return;} 20 build(lc,cl,md);build(rc,md+1,cr);up(p); 21 } 22 void chg(int l,int r,int v,int p=1,int cl=1,int cr=n){ 23 if(mx[p]<=v)return; 24 if(mn[p]==mx[p]&&l<=cl&&cr<=r){ 25 rb[p]-=(v-mn[p])*(tim-1ll); 26 mn[p]=mx[p]=lzc[p]=v;long long ra=A[p]; 27 A[p]=(cl-cr-1ll)*v;B[p]=ra*tim+B[p]-A[p]*tim;return; 28 }if(cl==cr)return;if(~lzc[p])tim--,down(p,cl,cr),tim++; 29 if(l<=md)chg(l,r,v,lc,cl,md); if(r>md)chg(l,r,v,rc,md+1,cr); up(p); 30 } 31 long long ask(int l,int r,int p=1,int cl=1,int cr=n){ 32 if(l<=cl&&cr<=r)return A[p]*tim+B[p]; 33 if(~lzc[p])down(p,cl,cr); 34 return (l<=md?ask(l,r,lc,cl,md):0)+(r>md?ask(l,r,rc,md+1,cr):0); 35 } 36 void modify(int i){ 37 if(a[i]<=n)v[a[i]].pop_back(),chg(*(--v[a[i]].end())+1,i-1,a[i]); 38 chg(i,i,0); 39 } 40 int main(){ 41 cin>>id>>n; 42 for(int i=1;i<=n;++i)scanf("%d",&a[i]),v[i].push_back(0);v[0].push_back(0); 43 for(int i=1;i<=n;++i)if(a[i]<S)v[a[i]].push_back(i); 44 pmn[0]=v[0][v[0].size()-1];mex[1]=1; 45 if(v[0].size()>1)for(int i=1;v[i].size()>1;++i)pmn[i]=min(pmn[i-1],v[i][v[i].size()-1]),mex[1]=i+1; 46 for(int i=2;i<=n;++i){ 47 mex[i]=mex[i-1]; 48 while(mex[i]&&pmn[mex[i]-1]<i)mex[i]--; 49 }build(1,1,n);tim=n; 50 cin>>Q;for(int i=1;i<=Q;++i)scanf("%d%d",&q[i].l,&q[i].r),q[i].o=i; 51 sort(q+1,q+1+Q); 52 for(int i=1;i<=Q;++i){ 53 while(tim>q[i].r+1)modify(tim),tim--; 54 ans[q[i].o]=q[i].r==n?0:-ask(q[i].l,q[i].r); 55 } 56 while(tim)modify(tim),tim--; 57 for(int i=1;i<=Q;++i)ans[q[i].o]+=ask(q[i].l,q[i].r); 58 for(int i=1;i<=Q;++i)printf("%lld\n",ans[i]); 59 }
T3:浑水摸鱼(waterflow)
大意:求数串最小表示法下本质不同的子串数。(最小表示法这里指出现最靠前的数字,所有与之相等的数都变成$1$,第二考前的种类变成$2..$)$n \le 5 \times 10^4$
做题时还真的就没想起来最小表示法的事。所谓最小表示法就是重新编号,第一种出现的赋为编号1,第二种是2,以此类推。
这题并不是什么高端字符串算法,而是。。。哈希
求本质不同的子串数,容易想到$SA$和$SAM$。然而因为最小表示法的存在$SAM$没法建边,考虑$SA$
可以发现,如果一个串的左端点确定了,那它的最小表示法就确定了。
用$SA$求本质不同的子串数的方法是求$height$然后用整个串减去重叠部分。但是第一步是需要我们把所有后缀排序。
如何在合理的复杂度内比较两个最小表示法之后的字符串?
先假定我们能处理出所有字符串的哈希值,那么就可以重载$sort$比较函数,二分哈希值找到第一个不相等的位然后比较就行。
求$height$的时候也同理,拍好序之后二分排名相等的两个后缀匹配位数就行。
问题在于求哈希值。
我们发现,左端点$i$右移一位的话,变化只是$A_i$下一次出现的位置上的数变成了$0$。每次只修改了一位。
所以用主席树维护,下标位置$i$维护$w_i \times p^i$。
这样问题就解决了。时间复杂度$sort+$二分$+$主席树,一共是$n log^3 \ n$
有一个细节,$STL \ sort$在排序时如果剩余区间较小考虑到常数问题它会选择$O(n^2)$暴力来,然而这对你已经有两个$log$的二分和主席树而言非常致命。
转而我们使用stable_sort。这是$STL$中一个基于归并排序的稳定排序,同时它在排序时相等的元素原下标在左的依旧在左。这样就不会被卡了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 1666666 4 #define ull unsigned long long 5 #define md (cl+cr>>1) 6 int a[S],n,lst[S],pc,dt[S],rt[S],nxt[S],lc[S],rc[S],sa[S],Ans;ull pw[S],w[S],iv[S],Iv; 7 ull qp(ull b,ull t,ull a=1){for(;t;t>>=1,b=b*b)if(t&1)a=a*b;return a;} 8 void build(int&p,int cl,int cr){ 9 p=++pc; 10 if(cl==cr){w[p]=(dt[cl]+1)*pw[cl];return;} 11 build(lc[p],cl,md);build(rc[p],md+1,cr);w[p]=w[lc[p]]+w[rc[p]]; 12 } 13 void insert(int&p,int P,int pos,int cl,int cr){ 14 p=++pc; 15 if(cl==cr){w[p]=pw[cl];return;} 16 if(pos<=md)insert(lc[p],lc[P],pos,cl,md),rc[p]=rc[P]; 17 else insert(rc[p],rc[P],pos,md+1,cr),lc[p]=lc[P]; 18 w[p]=w[lc[p]]+w[rc[p]]; 19 } 20 ull ask(int p,int l,int r,int cl=1,int cr=n){ 21 if(l<=cl&&cr<=r)return w[p]; 22 return (l<=md?ask(lc[p],l,r,cl,md):0)+(r>md?ask(rc[p],l,r,md+1,cr):0); 23 } 24 ull hsh(int l,int r){return r>n||r<l?-1:ask(rt[l],l,r)*iv[l];} 25 bool cmp(int a,int b){ 26 int l=0,r=n-max(a,b)+1,ans,x; 27 while(x=l+r>>1,l<=r)if(hsh(a,a+x)==hsh(b,b+x))l=x+1;else r=x-1,ans=x; 28 return (a+ans-dt[a+ans]<a?0:dt[a+ans])<(b+ans-dt[b+ans]<b?0:dt[b+ans]); 29 } 30 int main(){ 31 scanf("%d",&n);pw[0]=iv[0]=1;dt[n+1]=-1;Iv=qp(131ull,(1ull<<63)-1); 32 for(int i=1;i<=n;++i)scanf("%d",&a[i]),dt[i]=lst[a[i]]?i-lst[a[i]]:0,nxt[lst[a[i]]]=i,lst[a[i]]=i,pw[i]=pw[i-1]*131,iv[i]=iv[i-1]*Iv; 33 build(rt[1],1,n); 34 for(int i=1;i<n;++i){ 35 rt[i+1]=rt[i]; 36 if(nxt[i])insert(rt[i+1],rt[i],nxt[i],1,n); 37 } 38 for(int i=1;i<=n;++i)sa[i]=i; 39 stable_sort(sa+1,sa+1+n,cmp); Ans=n*(n+1ll)/2; 40 for(int i=2;i<=n;++i){ 41 int l=0,r=n-max(sa[i-1],sa[i])+1,ans,x; 42 while(x=l+r>>1,l<=r)if(hsh(sa[i],sa[i]+x-1)==hsh(sa[i-1],sa[i-1]+x-1))l=ans=x,l++;else r=x-1; 43 Ans-=ans; 44 }cout<<Ans<<endl; 45 }