CF526G Spiders Evil Plan
一个困扰了我许久的题。
给定一棵 \(n\) 个结点的无根树,边有边权。
有 \(q\) 次询问,每次给出 \(x,y\),你需要选择 \(y\) 条树上的路径,使这些路径形成一个包含 \(x\) 的连通块,且连通块中包含的边权和最大。
\(n,q \le 10^5\),边权为 \([1,1000]\) 内的正整数,强制在线
题解:
可以证明,这个问题相当于取出一个含有至多 \(2y\) 个叶子的连通块包含 \(x\),使边权和最大。
先考虑 \(y=1\) 的情况。相当于从 \(x\) 往外连两条不相交的链。其中一条一定是连向直径的某端点 \(r\),另一条则连向以 \(r\) 为根时 \(x\) 的子树内最远的点。
当 \(y\) 更大时,要做的就是在 \(y=1\) 的基础上每次找最远的叶子连出去,重复 \(2y-2\) 次即得到答案。
怎么维护?
首先我们发现第一次必选直径端点。因此考虑分别以直径两端点为根处理答案,最后取 \(\max\)。
定根后,假设现在不需要包含 \(x\),那是经典问题。最简单的做法是长链剖分然后把最长的 \(2y\) 条链取出来(这里长链剖分是带权的,也就是说,哪个子树有最远的叶子结点,哪个子树就定为长子树)。
现在我们强行要求包含 \(x\)。对于第一条路径,当然一端是根,而另一端则是 \(x\) 所在长链的链脚,我们称它为“钦定路径”。也就是说,原本第一步要选最长的长链,而现在则选了这条钦定路径取而代之。而由于钦定路径的存在,其所经过的原来的长链的上半段被砍掉变成钦定的了,但下半段仍是长链。
那么我们现在要做的是,对于每种钦定路径的情况,求出其余长链当中前 \(2y-2\) 大的的长度和。
假设可以离线,那么一种想法是把树 DFS 一遍(相当于钦定当前点到根的路径),然后把长链的长度扔到平衡树里面维护(向下走时相应长链的变短,回退时再变长,到查询点时查前 \(2y-2\) 大之和)。
但可以直接用线段树。因为所有情况下能出现的长链种数是 \(O(n)\) 的(一种和一个链顶一一对应),所以把这些可能长链从大到小排序放到线段树里,维护哪些是当前出现的,查询时线段树上二分即可。
要在线的话,就改成可持久化线段树吧。
#include<bits/stdc++.h>
const int N=1e5+3,K=20;
int n,m,p[2],dis[N],t0[N],ans;
bool Cmp(const int&i,const int&j){return t0[i]>t0[j];}
struct edge{int v,c;};
std::vector<edge>g[N];
void Dfs0(int u,int fa,int f){
int v;
for(int i=0;i<g[u].size();i++)
if((v=g[u][i].v)!=fa)
dis[v]=dis[u]+g[u][i].c,Dfs0(v,u,f);
if(dis[u]>dis[p[f]])p[f]=u;
}
#define M (L+R>>1)
struct solve{
int root,rt[N],dis[N],son[N],top[N],fot[N],hei[N],p[N],q[N],ls[N*K*2],rs[N*K*2],c[N*K*2],s[N*K*2],t;
inline void Up(int k){c[k]=c[ls[k]]+c[rs[k]],s[k]=s[ls[k]]+s[rs[k]];}
void Build(int L,int R,int&k){
k=++t;
if(L==R){
c[k]=top[p[L]]==p[L];
s[k]=c[k]*hei[p[L]];
return;
}
Build(L,M,ls[k]),Build(M+1,R,rs[k]);
Up(k);
}
void Update(int i,int L,int R,int g,int&k){
k=++t;
ls[k]=ls[g],rs[k]=rs[g],c[k]=c[g],s[k]=s[g];
if(L==R){
c[k]^=1;
s[k]=c[k]*hei[p[L]];
return;
}
i<=M?Update(i,L,M,ls[g],ls[k]):Update(i,M+1,R,rs[g],rs[k]);
Up(k);
}
int Query(int x,int L,int R,int k){
if(!x)return 0;
if(L==R)return s[k];
if(c[ls[k]]>=x)
return Query(x,L,M,ls[k]);
else
return Query(x-c[ls[k]],M+1,R,rs[k])+s[ls[k]];
}
void Dfs1(int u,int fa){
int v;
for(int i=0;i<g[u].size();i++)
if((v=g[u][i].v)!=fa){
dis[v]=dis[u]+g[u][i].c;
Dfs1(v,u);
hei[v]+=g[u][i].c;
if(hei[v]>hei[u]){
son[u]=v;
hei[u]=hei[v];
}
}
}
void Dfs2(int u,int fa){
int v;
top[u]=top[u]?top[u]:u;
fot[top[u]]=u;
if(son[u])top[son[u]]=top[u],Dfs2(son[u],u);
fot[u]=fot[top[u]];
for(int i=0;i<g[u].size();i++)
if((v=g[u][i].v)!=fa&&v!=son[u])Dfs2(v,u);
}
void Dfs3(int u,int fa){
int v;
Update(q[u],1,n,rt[fa],rt[u]);
if(son[u])Update(q[son[u]],1,n,rt[u],rt[u]);
for(int i=0;i<g[u].size();i++)
if((v=g[u][i].v)!=fa)
Dfs3(v,u);
}
void Init(int Root){
int u;
root=Root;
Dfs1(root,0);
Dfs2(root,0);
for(u=1;u<=n;u++)p[u]=u,t0[u]=hei[u];
std::sort(p+1,p+1+n,Cmp);
for(u=1;u<=n;u++)q[p[u]]=u;
Build(1,n,rt[0]);
Dfs3(root,0);
}
inline int Ans(int u,int x){
return dis[fot[u]]+Query((x-1)*2,1,n,rt[fot[u]]);
}
}slv[2];
int main(){
int u,v,x;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d%d",&u,&v,&x);
g[u].push_back((edge){v,x});
g[v].push_back((edge){u,x});
}
dis[1 ]=0,p[0]=1 ,Dfs0(1 ,0,0);
dis[p[0]]=0,p[1]=p[0],Dfs0(p[0],0,1);
slv[0].Init(p[0]);
slv[1].Init(p[1]);
for(;m--;){
scanf("%d%d",&u,&x),u=(u+ans-1)%n+1,x=(x+ans-1)%n+1;
printf("%d\n",ans=std::max(slv[0].Ans(u,x),slv[1].Ans(u,x)));
}return 0;
}