【考试总结】2022-04-13
挖宝
通过路径长度总和可以计算出来这个 \(x\) 点和 \(a\sim b\) 路径的垂足
如果这个点不是 \(\rm LCA(a,b)\) 那么可以求出来 \(x\) 的深度,每个深度开一个线段树,下标是 \(\rm dfn\) 即可
如果垂足是 \(\rm LCA\) 那么仍然需要考虑子树里面的情况,否则需要计算是不是存在一个向上走的路径使得长度足够
我的做法是先对全树进行重链剖分,每个点计算除重儿子子树外能延伸的最长链长度 \(d_x\)
考察距离具体贡献形式可以发现此时对 \(d_x-dep_x\) 在重链上做前缀 \(\arg\max\) ,每次查询暴力跳重链,轻链暴力查扒掉某一子树区间内的信息即可
实现的时候注意所谓 fa[top[x]]
这个点已经查询过了,所以求重链 \(\arg \max\) 时需要再跳一步父亲
复杂度是 \(\Theta(q\log^2n)\) 的
Code Display
const int N=1e6+10;
int n,Q;
vector G[N];
int fa[N],son[N],siz[N],dfn[N],dep[N],ord[N],tim,top[N];
const int M=N*20;
int ls[M],rs[M],rt[N],tot;
vector nds[N];
inline void insert(int &p,int l,int r,int pos){
if(!p) p=++tot; if(l==r) return ;
int mid=(l+r)>>1;
if(pos<=mid) insert(ls[p],l,mid,pos);
else insert(rs[p],mid+1,r,pos);
return ;
}
inline int query(int p,int l,int r,int st,int ed){
if(ed>1,res=-1;
if(st<=mid) res=query(ls[p],l,mid,st,ed);
if(~res) return res;
if(ed>mid) res=query(rs[p],mid+1,r,st,ed);
return res;
}
int val[N],len[N];
inline void dfs1(int x,int fat){
dep[x]=dep[fa[x]=fat]+(siz[x]=1);
for(auto t:G[x]) if(t!=fat){
dfs1(t,x); siz[x]+=siz[t];
if(siz[t]>siz[son[x]]) son[x]=t;
ckmax(len[x],len[t]+1);
}
return ;
}
inline void dfs2(int x,int topf){
ord[dfn[x]=++tim]=x; top[x]=topf;
insert(rt[dep[x]],1,n,dfn[x]);
if(son[x]) dfs2(son[x],topf);
for(auto t:G[x]) if(!dfn[t]) dfs2(t,t),ckmax(val[x],len[t]+1);
val[x]-=dep[x];
return ;
}
inline int get_lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]dep[y]?y:x;
}
int argmax[N];
inline int jump(int x,int d){
while(d>dep[x]-dep[top[x]]) d-=dep[x]-dep[fa[top[x]]],x=fa[top[x]];
return nds[top[x]][dep[x]-dep[top[x]]-d];
}
inline int get_son(int y,int x){
int lst=-1;
while(top[x]!=top[y]) y=fa[lst=top[y]];
if(y==x) return lst;
else return son[x];
}
signed main(){
n=read(); Q=read();
for(int i=1;ival[argmax[lst]]) argmax[x]=x;
else argmax[x]=argmax[lst];
x=son[lst=x];
}
}
while(Q--){
int a=read(),da=read(),b=read(),db=read();
int lca=get_lca(a,b),dis=dep[a]+dep[b]-2*dep[lca];
if(da+dbdep[b]) swap(a,b),swap(da,db);
int waste=(da+db-dis)/2;
if(da=D) x=jump(a,D);
else{
D-=dep[a]-dep[lca];
D=dep[b]-dep[lca]-D;
x=jump(b,D);
}
if(x==lca){
if(dep[x]>waste){
print(jump(x,waste));
continue;
}
int t1=get_son(b,x);
if(a==lca){
int tar=dep[x]+waste;
int ans=-1;
if(~(ans=query(rt[tar],1,n,dfn[lca],dfn[t1]-1))){
print(ord[ans]);
continue;
}
ans=query(rt[tar],1,n,dfn[t1]+siz[t1],dfn[x]+siz[x]-1);
if(~ans){
print(ord[ans]);
continue;
}
}else{
int t2=get_son(a,x),tar=dep[x]+waste;
if(dfn[t1]>dfn[t2]) swap(t1,t2);
int ans=query(rt[tar],1,n,dfn[x],dfn[t1]-1);
if(~ans){
print(ord[ans]);
continue;
}
ans=query(rt[tar],1,n,dfn[t1]+siz[t1],dfn[t2]-1);
if(~ans){
print(ord[ans]);
continue;
}
ans=query(rt[tar],1,n,dfn[t2]+siz[t2],dfn[x]+siz[x]-1);
if(~ans){
print(ord[ans]);
continue;
}
}
int ori=x,lst=x;
while(x){
if(x==lst){
if(x!=top[x]) x=fa[x];
else goto Tag;
}
if(dep[ori]+val[argmax[x]]>=waste){
x=argmax[x];
int tar=waste-(dep[ori]-dep[x])+dep[x];
print(ord[query(rt[tar],1,n,dfn[son[x]]+siz[son[x]],dfn[x]+siz[x]-1)]);
goto Here;
}
Tag:;
int pre=top[x];
lst=x=fa[top[x]];
int tar=waste-(dep[ori]-dep[x])+dep[x];
int ans=query(rt[tar],1,n,dfn[x],dfn[pre]-1);
if(~ans){
print(ord[ans]);
goto Here;
}
ans=query(rt[tar],1,n,dfn[pre]+siz[pre],dfn[x]+siz[x]-1);
if(~ans){
print(ord[ans]);
goto Here;
}
}
puts("-1");
Here:;
}else{
int tson=0;
if(x==a||x==b) tson=0;
else if(get_lca(x,a)==x) tson=get_son(a,x);
else tson=get_son(b,x);
int tar=dep[x]+waste;
if(tson){
int ans=query(rt[tar],1,n,dfn[x],dfn[tson]-1);
if(~ans){
print(ord[ans]);
continue;
}
ans=query(rt[tar],1,n,dfn[tson]+siz[tson],dfn[x]+siz[x]-1);
if(~ans){
print(ord[ans]);
continue;
}
}else{
int ans=query(rt[tar],1,n,dfn[x],dfn[x]+siz[x]-1);
if(~ans){
print(ord[ans]);
continue;
}
}
print(-1);
}
}
return 0;
}
图
使用最小表示法记录最后 \(12\) 个点属于哪些连通块的做法可以通过,并且运行速度出奇快
标算考虑对 \(\bmod\ 2\) 做文章:如果某种选点方案出现了 \(t\) 个连通块,如果使之造成 \(2^{t-1}\) 的贡献就可以将不合法的情况消去了
那么转变成求 \(2^t\) 模 \(4\) 的余数再除二即可
此时变成出现了 \(t\) 个连通块就造成 \(2^t\) 的贡献,这个可以通过黑白染色来刻画:每个点有 \(3\) 个状态 \(0/1/2\) ,每个边的两个端点状态全不为 \(2\) 就必须满足状态一样
使用 \(3\) 进制状压可以解决这个问题,复杂度是合理的 \(\Theta(n3^{12})\)
Code Display
int n,m,Bas;
vector lar[100],G[100];
set dp[100];
inline int encode(vector sta){
int S=0;
for(int i=0;i decode(int S){
vector res;
for(int i=Bas-1;~i;--i) res.emplace_back(S%(1+Bas)),S/=(1+Bas);
reverse(res.begin(),res.end());
return res;
}
struct DSU{
int fa[100];
inline void init(int l,int r){rep(i,l,r) fa[i]=i; return ;}
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void merge(int x,int y){fa[find(x)]=find(y); return ;}
}Dsu;
int id[100];
signed main(){
n=read(); m=read();
for(int i=1;i<=m;++i){
int u=read(),v=read();
if(u vis(n+1),mark(n+1);
for(int i=1;i nds;
rep(j,1,n) if(i>>(j-1)&1) mark[j]=1,vis[j]=0,nds.emplace_back(j);
function dfs=[&](const int x){
if(vis[x]) return ; vis[x]=1;
for(auto t:G[x]) if(mark[t]) dfs(t);
for(auto t:lar[x]) if(mark[t]) dfs(t);
};
dfs(nds.back());
bool dlt=1;
rep(j,1,n) if(i>>(j-1)&1){
dlt&=vis[j];
vis[j]=0;
mark[j]=0;
}
ans^=dlt;
}
print(ans);
exit(0);
}
int S=1<>(j-1)&1){
for(auto t:G[j]) if(i>>(t-1)&1) Dsu.merge(j,t);
}
vector sta(Bas);
int num=0;
for(int j=1;j<=Bas;++j) if(i>>(j-1)&1){
if(!id[Dsu.find(j)]) id[Dsu.find(j)]=++num;
sta[j-1]=id[Dsu.find(j)];
}
rep(j,1,Bas) id[j]=0;
dp[Bas].insert(encode(sta));
}
int only=1;
for(int i=1;i<=Bas-1;++i) only*=Bas+1;
for(int i=Bas;i vec=decode(sta);
auto get_num=[&](vector &cur){
int cnt=0;
for(int j=1;j nxt(Bas);
int num=0;
rep(j,1,Bas-1) if(vec[j]){
if(!id[vec[j]]) id[vec[j]]=++num;
nxt[j-1]=id[vec[j]];
}
rep(j,1,Bas-1) if(vec[j]) id[vec[j]]=0;
int tmp=encode(nxt);
if(dp[i+1].count(tmp)) dp[i+1].erase(tmp);
else dp[i+1].insert(tmp);
}
if(sta==only) ans^=1;
Dsu.init(i-Bas+1,i+1);
vector lst(Bas+1,-1);
int num=0;
rep(j,0,Bas-1) if(vec[j]){
int cur=i-Bas+1+j;
if(~lst[vec[j]]) Dsu.merge(cur,lst[vec[j]]);
lst[vec[j]]=cur;
}
bool linked=0;
for(auto t:G[i+1]) if(vec[Bas-(i+1-t)]!=0){
Dsu.merge(i+1,t);
linked|=t==i-Bas+1;
}
if(vec[0]==0||linked||gnum){
vector nxt(Bas);
int num=0;
rep(j,1,Bas) if(j==Bas||vec[j]){
int cur=i+1-Bas+j;
if(!id[Dsu.find(cur)]) id[Dsu.find(cur)]=++num;
nxt[j-1]=id[Dsu.find(cur)];
}
rep(j,1,Bas) if(j==Bas||vec[j]) id[Dsu.find(i+1-Bas+j)]=0;
int tmp=encode(nxt);
if(dp[i+1].count(tmp)) dp[i+1].erase(tmp);
else dp[i+1].insert(tmp);
}
}
}
if(dp[n].count(0)) dp[n].erase(0);
else dp[n].insert(0);
for(auto t:dp[n]){
vector vec=decode(t);
for(auto v:vec) if(v>1) goto Here;
ans^=1;
Here:;
}
print(ans);
return 0;
}
字符串
考虑将相似刻画为 \(\rm LCP+LCS\ge m-1\) ,所以建立正反两个后缀自动机让它变成一道 \(\text{Data Structure}\) 题
在其中一棵树 \(T_1\) 上 \(\text{Dsu On Tree}\),并在过程中维护一个点集
将某个点 \(x\) 加入点集时首先计算点集中的点对其造成的贡献,\(x\) 在 \(T_2\) 上的对应点 \(y\) ,那么找到 \(len_x+len_{y\to anc}\ge m-1\) 的最浅 \(anc\) 并求当前点集在 \(T_2\) 中的对应点有几个在 \(anc\) 的子树里面
子树信息和使用 \(\rm Fenwick\ Tree\) 即可维护
而它对点集中现在 在 \(anc\) 子树里面的点的答案造成 \(1\) 的贡献,这个问题是子树加单点查询
另外维护一个树状数组,在加入点集时减去对 \(\rm dfn\) 单点查询的信息,从点集中删掉时加上对 \(\rm dfn\) 单点查询的信息就可以查询这个点在点集中时有多少点对其造成了贡献
实现的时候注意 加删点/查询增量 的顺序,即轻儿子统一加入点集时要先在 \(T_2\) 加入再删版本信息
Code Display
const int N=2e5+10;
int n,m,ans[N];
char s[N];
struct Suffix_Automation{
vector G[N];
int len[N],fa[N],son[N][26],tot=1,las=1,pos[N];
int mark[N];
inline void extend(int x,int id){
int tmp=las,np=las=++tot; len[np]=len[tmp]+1;
pos[mark[np]=len[np ]]=np;
while(!son[tmp][x]) son[tmp][x]=np,tmp=fa[tmp];
if(!tmp) return fa[np]=1,void();
int q=son[tmp][x];
if(len[q]==len[tmp]+1) return fa[np]=q,void();
int clone=++tot;
len[clone]=len[tmp]+1; fa[clone]=fa[q]; fa[q]=fa[np]=clone;
for(int i=0;i<26;++i) son[clone][i]=son[q][i];
while(son[tmp][x]==q) son[tmp][x]=clone,tmp=fa[tmp];
return ;
}
inline void init(){
for(int i=2;i<=tot;++i) G[fa[i]].emplace_back(i);
return ;
}
}Pre,Suf;
int bz[N][20],in[N],out[N],tim;
inline void dfs1(int x,int fat){
bz[x][0]=fat;
in[x]=++tim;
for(int i=1;bz[x][i-1];++i) bz[x][i]=bz[bz[x][i-1]][i-1];
for(auto t:Pre.G[x]) dfs1(t,x);
out[x]=tim;
return ;
}
inline int get_anc(int x,int tar){
for(int i=19;~i;--i) if(bz[x][i]&&Pre.len[bz[x][i]]>=tar) x=bz[x][i];
return x;
}
struct Fenwick_Tree{
int c[N];
inline void insert(int x,int v){
for(;x<=tim;x+=x&(-x)) c[x]+=v;
}
inline int query(int x){
int res=0;
for(;x;x-=x&(-x)) res+=c[x];
return res;
}
}T1,T2;
int son[N],siz[N];
inline void get_son(int x){
siz[x]=(bool)Suf.mark[x];
for(auto t:Suf.G[x]){
get_son(t); siz[x]+=siz[t];
if(siz[t]>siz[son[x]]) son[x]=t;
}
return ;
}
vector sub[N];
int mp[N],anc[N];
auto ins_node=[](const int x,const int coef){
T2.insert(in[anc[x]],coef);
T2.insert(out[anc[x]]+1,-coef);
return ;
};
auto query=[](const int x){
return T1.query(out[x])-T1.query(in[x]-1);
};
inline void solve(int x,int opt){
for(auto t:Suf.G[x]) if(t!=son[x]) solve(t,0);
if(son[x]) solve(son[x],1),swap(sub[x],sub[son[x]]);
if(Suf.mark[x]>=m){
anc[x]=get_anc(mp[x],m-1-Suf.mark[x]);
ans[Suf.mark[x]]+=query(anc[x]);
ins_node(x,1);
T1.insert(in[mp[x]],1);
ans[Suf.mark[x]]-=T2.query(in[mp[x]]);
sub[x].emplace_back(x);
// Insert the node x itself
}
for(auto t:Suf.G[x]) if(t!=son[x]){
for(auto node:sub[t]){
anc[node]=get_anc(mp[node],m-1-Suf.len[x]);
ans[Suf.mark[node]]+=query(anc[node]);
ins_node(node,1);
}
for(auto node:sub[t]){
T1.insert(in[mp[node]],1);
ans[Suf.mark[node]]-=T2.query(in[mp[node]]);
sub[x].emplace_back(node);
}
}// Insert the node from the subtree of x's ligth sons
if(!opt){
for(auto node:sub[x]) ans[Suf.mark[node]]+=T2.query(in[mp[node]]);
for(auto node:sub[x]){
ins_node(node,-1);
T1.insert(in[mp[node]],-1);
}
// Remove the subtree of x from the nodes set
}
return ;
}
signed main(){
n=read(); m=read(); scanf("%s",s+1);
for(int i=n;i;--i) Pre.extend(s[i]-'a',i);
Pre.init();
dfs1(1,0);
for(int i=1;i<=n;++i) Suf.extend(s[i]-'a',i);
Suf.init();
for(int i=1;i<=Suf.tot;++i) if(Suf.mark[i]>=m) mp[i]=Pre.pos[n+m-Suf.mark[i]];
get_son(1);
solve(1,0);
rep(i,m,n) print(ans[i]); putchar('\n');
return 0;
}