但愿这套题不是花钱买来的,在$loj$上摆着呢,草率的打包了一下题面题目名草率的弄成$ABC$就扔上来了。
雅礼集训的题。其实不难。但是我把$T1$想复杂之后剩下两道题就都没时间了。。。
关键好像还是个原题。没印象,想了这么久。。。
$T3$保证有解但是题目没说还害得我以为读错题了又是浪费了时间。
还是脑子不够灵活。。或者说,没有尝试转变思路。。。
T1:A/事情的相似度
大意:给定$01$串多次询问给出$l,,r$求$maxlimits_{l le i<j le r}lcs(prefix(i),prefix(j))$。$n,q le 10^5,$
好像主流写法都是$SAM$只有我写的$SA$。真就是脑子不行。
而且这又是原题?为啥一点印象都没有?啥都没记住啊,从头开始想的,所以就偏离了正路想的怪麻烦的。
然而和题解是一样的,时间复杂度也和大家一样。就这样吧。
首先肯定字符串翻转一下$prefix$变$suffix$,$lcs$变$lcp$。顺手的多。
首先求一个$SA$。然而我忘了板子了于是用的$hash$二分,复杂度$O(nlog^2n)$
然后求出$height$。呃我也不会,再$hash$二分,复杂度$O(nlogn)$
然后我们从大到小枚举$height$值。假如目前枚举到了$c$,且位置$x$满足$height[x]=c$
也就是说$lcp(sa[i-1],sa[i])=c$。于是我们知道,如果一次询问同时包含了$sa[i-1],sa[i]$的话那么这个询问的答案至少是$c$
扩展开来,上述过程类似于合并,我们建立一个并查集每次将$sa[i-1],sa[i]$合并。
那么我们就能知道,如果一次询问同时包含了两个在同一个并查集中的元素,那么它的答案就至少是$c$
我们用一个三元组表示这样的关系,$(i,j,c)$表示如果一组询问同时包含了$i,j$那么答案就对$c$取$max$。考虑如何求出所有三元组。
我们对每个并查集维护一个$set$里面存储属于这个并查集的元素。
然后每次合并两个并查集$X,Y$的时候,对于任意$i in X,j in Y$,都有$(i,j,c)$是一个我们需要的三元组。
这样就得到了所有的三元组。是$O(n^2)$级别的。
先考虑三元组怎么用来处理询问,再去优化它的数量。
对于$(i,j,c)$这个三元组,因为我们知道询问的是一个区间,是连续的,所以包含$i,j$也就意味着包含了$[i,j]$这个区间。
我们发现这题没有强制在线,于是将询问离线下来。扫描线。把所有询问和三元组的区间均按照右端点排序。
然后这样枚举右端点不断插入后进行询问,就能满足询问的右端点$geq$三元组的右端点。
接下来只需要满足询问的左端点$le$三元组左端点,这个就是一个朴素的一维偏序问题了,拿个树状数组维护就好了。
每次的操作是单点修改,后缀求$max$。翻转一下就很好做了。
因为三元组的数量是$n^2$的所以总的复杂度是$O(n^2logn+mlogn)$的。
然而我们不难发现三元组中有许多冗余。而且合并的过程可以优化。
首先我们如果合并$set$时使用启发式合并,那么这部分的复杂度就合法了。
然后我们同样用启发式合并的思路,原来是枚举$i in X,j in Y$。这次我们转而只枚举较小的一个,不妨设$|X|<|Y|$,于是我们枚举$i in X$
假设$Y$中的元素是$A,B,C,D,E,F$且满足$A<B<C<i<D<E<F$。我们就会加入一些三元组$(A,i,c),(B,i,c),(C,i,c),(i,D,c),(i,E,c),(i,F,c)$
然而我们发现对于前三个三元组,它们的含义是,如果包含$[A,i],[B,i],[C,i]$这些区间中的一个,那么答案对$c$取$max$。
但是我们知道如果一个区间包含了$[A,i]$那么也必然包含它的子区间$[C,i]$,答案都是对$c$取$max$。所以$A$这个点的限制其实是没有用的。
同理$[i,D]$的存在也使$[i,E],[i,F]$的存在失去的意义。所以我们发现我们枚举较小的$set$之后只需要在另一个$set$中找到当前元素的前驱后继构成三元组就足够了。
在启发式合并中只枚举较小的一个,总枚举量是$O(nlogn)$的,每次只会加入两个三元组所以三元组总数也是这个级别。查前驱后继的复杂度一共是$O(nlog^2n)$
所以总体的复杂度就下降到了$O(nlog^2n+mlogn)$。常熟较大。(连$SA$都是$hash$二分求的我有什么脸说常数。。。)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 100005 4 #define ull unsigned long long 5 int n,m,sa[S],h[S],f[S],ans[S],lcnt,t[S]; ull hsh[S],pw[S]; char s[S]; 6 void mdf(int p,int v){for(;p<=n;p+=p&-p)t[p]=max(t[p],v);} 7 int ask(int p,int a=0){for(;p;p^=p&-p)a=max(a,t[p]);return a;} 8 set<int>pt[S]; vector<int>v[S]; 9 struct lim{int a,b,w;friend bool operator<(lim x,lim y){return x.b<y.b;}}l[S<<8]; 10 struct qs{int l,r,o;friend bool operator<(qs x,qs y){return x.r<y.r;}}q[S]; 11 ull Hsh(int l,int r){return hsh[r]-hsh[l-1]*pw[r-l+1];} 12 int find(int x){return f[x]==x?x:f[x]=find(f[x]);} 13 bool cmp(int la,int lb){ 14 int r=max(n-la+1,n-lb+1),l=0,ans,md; 15 while(md=l+r>>1,l<=r)if(Hsh(la,la+md-1)==Hsh(lb,lb+md-1))l=ans=l+r>>1,l++;else r=(l+r>>1)-1; 16 if(la+ans>n)return 1; if(lb+ans>n)return 0; 17 return s[lb+ans]-48; 18 } 19 int getsame(int la,int lb){ 20 int r=max(n-la+1,n-lb+1),l=0,ans,md; 21 while(md=l+r>>1,l<=r)if(Hsh(la,la+md-1)==Hsh(lb,lb+md-1))l=ans=l+r>>1,l++;else r=(l+r>>1)-1; 22 return ans; 23 } 24 void merge(int a,int b,int val){ 25 a=find(a);b=find(b); 26 if(pt[a].size()<pt[b].size())swap(a,b); 27 f[b]=a; 28 for(auto j:pt[b]){ 29 auto x=pt[a].lower_bound(j); 30 if(x!=pt[a].begin())x--,l[++lcnt]=(lim){*x,j,val},x++; 31 if(x!=pt[a].end())l[++lcnt]=(lim){j,*x,val}; 32 } 33 for(auto j:pt[b])pt[a].insert(j); pt[b].clear(); 34 } 35 int main(){ 36 scanf("%d%d%s",&n,&m,s+1); pw[0]=1; reverse(s+1,s+1+n); 37 for(int i=1;i<=n;++i)pw[i]=pw[i-1]*11,hsh[i]=hsh[i-1]*11+s[i]-48,sa[i]=f[i]=i,pt[i].insert(i); 38 stable_sort(sa+1,sa+1+n,cmp); 39 for(int i=2;i<=n;++i)h[i]=getsame(sa[i-1],sa[i]),v[h[i]].push_back(i); 40 for(int i=n;i;--i)for(auto j:v[i])merge(sa[j-1],sa[j],i); 41 for(int i=1,x,y;i<=m;++i)scanf("%d%d",&x,&y),q[i]=(qs){n+1-y,n+1-x,i}; 42 sort(q+1,q+1+m);sort(l+1,l+1+lcnt); 43 int ptl=1,ptq=1; 44 for(int i=1;i<=n;++i){ 45 while(l[ptl].b==i)mdf(n+1-l[ptl].a,l[ptl].w),ptl++; 46 while(q[ptq].r==i)ans[q[ptq].o]=max(ans[q[ptq].o],ask(n+1-q[ptq].l)),ptq++; 47 } 48 for(int i=1;i<=m;++i)printf("%d ",ans[i]); 49 }
T2:B/跳蚤王国的宰相
大意:树,每次操作可以断一条边连一条边,对于每个点求解:至少要操作几次才能使得这个点成为树上到所有点距离和最近的点。$n le 10^6$
说了那么多,其实就是要让每个点成为重心(竟然没看出来。。。
于是我们先求出原树的重心,以之为根后,每个子树的大小都不超过$n/2$。
我们要拆边连边使某个点的所有子树都不超过$n/2$。
分类讨论。若当前求解的点叫做$x$,原树重心叫做$C$,$x$所在的$C$的子树的根是$y$。
那么显然,你只会断与$C$直接相连的边。否则因为拆出来的树都不大于$n/2$,自然越大越优。
我们只要把拆出来的树直接接在$x$身上,因为拆出来的大小不超过$n/2$,所以只要没被拆过的$x$子树外的部分不超过$n/2$即可。
所以我们把$C$的每个子树大小排序,前缀和一下,对于每个点分类讨论是否拆$C-y$这条边,然后二分即可。$O(nlogn)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 2000005 4 int sz[S],C,n,fir[S],l[S],to[S],ec,v[S],vc,bl[S],sum[S],pos[S]; 5 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 6 void dfs(int p,int fa=0,int anc=0,int o=0){ 7 sz[p]=1;int mxs=0; if(o==1)anc=p;bl[p]=anc; 8 for(int i=fir[p];i;i=l[i])if(to[i]!=fa)dfs(to[i],p,anc,o+1),sz[p]+=sz[to[i]],mxs=max(mxs,sz[to[i]]); 9 mxs=max(mxs,n-sz[p]); 10 if(mxs<=n>>1)C=p; 11 } 12 int main(){ 13 cin>>n; 14 for(int i=1,x,y;i<n;++i)scanf("%d%d",&x,&y),link(x,y),link(y,x); 15 dfs(1);dfs(C); 16 int mx=0; 17 for(int i=fir[C];i;i=l[i])v[++vc]=sz[to[i]],mx=max(mx,sz[to[i]]); 18 sort(v+1,v+1+vc);reverse(v+1,v+1+vc); 19 for(int i=1;i<=vc;++i)sum[i]=sum[i-1]+v[i],pos[v[i]]=pos[v[i]]?pos[v[i]]:i; 20 for(int i=1;i<=n;++i)if(i!=C){ 21 int z=sz[bl[i]],t=pos[z],p=lower_bound(sum,sum+t,n-n/2-sz[i])-sum; 22 printf("%d ",p<t?p:min(lower_bound(sum+t,sum+vc+1,n-n/2+z-sz[i])-sum-1,lower_bound(sum+t,sum+vc+1,n-n/2+sz[i]-sz[i])-sum)); 23 }else puts("0"); 24 }
T3:C/蛐蛐国的修墙方案
大意:给定一个排列$P$,构造一个合法括号序列,满足所有的$s_i='('$的位置$i$向$p_i$连边之后每个点度数都是$1$。$n le 100$
保证有解。多解输出一种。
发现排列这个东西弄成环就好了,然后就是个类似奇偶染色的东西。
大小为$2$的环显然左边放左括号右边放右括号。剩下的环大小至少为$4$。状压枚举状态就行。
我不知道出题人无聊的卡正向搜索是想表达什么,很弱智就是了。时间复杂度$O(2^{frac{n}{4}} n)$
弱鸡数据为了卡正向搜索甚至连不缩环直接一位一位剪枝的倒序搜索都卡不掉,佛了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,ans[111],to[111],q[111],t; 4 int main(){ 5 cin>>n; 6 for(int i=1;i<=n;++i)cin>>to[i]; 7 for(int i=1;i<=n;++i)if(!ans[i]){ 8 int j=to[i],mn=i,op=40,cnt=1; 9 while(j!=i)mn=min(mn,j),j=to[j],cnt++; 10 while(!ans[mn])ans[mn]=op,op^=1,mn=to[mn]; 11 if(cnt>2)q[++t]=mn; 12 } 13 for(int i=0;i<1<<t;++i){ 14 for(int j=1;j<=t;++j){ 15 int p=q[j],op=40+(i&1<<j-1?0:1); 16 ans[p]=op;op^=1;p=to[p]; 17 while(p!=q[j])ans[p]=op,p=to[p],op^=1; 18 } 19 for(int i=1,v=0;i<=n;++i){ 20 v+=ans[i]==40?1:-1; 21 if(v<0)goto x; 22 }break;x:; 23 } 24 for(int i=1;i<=n;++i)putchar(ans[i]); 25 }