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;
}

posted on 2021-05-21 16:41  Dreamunk  阅读(134)  评论(0编辑  收藏  举报

导航