CF526G Spiders Evil Plan

非常不错的一道题。

题解

首先我们考虑没有 (x) 的限制,如果我们选择 (y) 条路径,最优的选法是什么?

首先可以证明,最后的 (y) 条路径必然是一个连通块,因为如果不是一个连通块,必然可以通过交换两条路径的交点来合并连通块,于是最后就合并为了一个连通块。这样的话,问题就被我们转换为了选择 $ 2y$ 个叶子,使得这些叶子所包含的连通块边权最大。

我们易证得直径的一端点必然位于答案中,这样如果我们以直径的一端点为根,问题就转化为了选择 (2y-1) 个叶子,打通其到根的路径,问连通块边权最大多少,这是一个长剖的板题

但是考虑到有多次询问,需要预处理。

以上就是没有 (x) 的限制的时候该怎么做,如果考虑到 (x) 的限制,意味着我们需要删掉一个叶子再加入 (x) 所在长链的叶子节点,可以证明(反证法什么的),删掉的叶子只能是两种情况:

  1. 其是排名为 (2y-1) 的长链。
  2. 是加入的 (x) 所在长链碰到的第一个叶子。

然后用倍增暴力维护一下就行了。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,q,rt1,rt2,lstans=0;
struct Edge{int nxt,to,val;}e[N<<1];int fir[N];
void add(int u,int v,int w,int i){e[i]=(Edge){fir[u],v,w},fir[u]=i;}
namespace find_diameter{
	int dep[N];
	void find(int u,int fa,int &res){
		if(dep[u]>dep[res]) res=u;
		for(int i=fir[u];i;i=e[i].nxt){
			int v=e[i].to;if(v==fa) continue;
			dep[v]=dep[u]+e[i].val,find(v,u,res);
		}
	}
	int main(){
		dep[1]=0,find(1,0,rt1);
		dep[rt1]=0,find(rt1,0,rt2);
		return 0;
	}
}
struct Node{int fa[20],sum[20],son,dep,dis,top;}tr[2][N];
int len[2],ord[2][N],sum[2][N];priority_queue<pair<int,int> > s;
void dfs1(int u,int tag){
	tr[tag][u].top=u;
	for(int i=fir[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==tr[tag][u].fa[0]) continue;
		tr[tag][v].fa[0]=u,tr[tag][v].dep=tr[tag][u].dep+e[i].val;
		dfs1(v,tag),tr[tag][v].dis+=e[i].val,tr[tag][v].sum[0]=e[i].val;
		tr[tag][u].dis=max(tr[tag][u].dis,tr[tag][v].dis);
		if(tr[tag][v].dis>=tr[tag][tr[tag][u].son].dis) tr[tag][u].son=v;
	}
}
void dfs2(int u,int tag){
	if(tr[tag][u].son){
		tr[tag][tr[tag][u].son].top=tr[tag][u].top;
		dfs2(tr[tag][u].son,tag);
	}
	for(int i=fir[u];i;i=e[i].nxt){
		int v=e[i].to;if(v!=tr[tag][u].fa[0]&&v!=tr[tag][u].son) dfs2(v,tag);
	}
}
void init(){
	for(int j=1;j<20;++j){
		for(int i=1;i<=n;++i){
			tr[0][i].fa[j]=tr[0][tr[0][i].fa[j-1]].fa[j-1];
			tr[0][i].sum[j]=tr[0][i].sum[j-1]+tr[0][tr[0][i].fa[j-1]].sum[j-1];
			tr[1][i].fa[j]=tr[1][tr[1][i].fa[j-1]].fa[j-1];
			tr[1][i].sum[j]=tr[1][i].sum[j-1]+tr[1][tr[1][i].fa[j-1]].sum[j-1];
		}
	}
	while(!s.empty()) s.pop();
	for(int i=1;i<=n;++i){
		if(tr[0][i].top==i) s.push(make_pair(tr[0][i].dis,i));
	}
	while(!s.empty()){
		ord[0][s.top().second]=++len[0];
		sum[0][len[0]]=sum[0][len[0]-1]+s.top().first,s.pop();
	}
	while(!s.empty()) s.pop();
	for(int i=1;i<=n;++i){
		if(tr[1][i].top==i) s.push(make_pair(tr[1][i].dis,i));
	}
	while(!s.empty()){
		ord[1][s.top().second]=++len[1];
		sum[1][len[1]]=sum[1][len[1]-1]+s.top().first,s.pop();
	}
}
int solve1(int x,int k,int tag){
	int tmp=x,res=tr[tag][x].dis-tr[tag][tmp].sum[0];
	for(int j=19;j>=0;--j){
		if(ord[tag][tr[tag][tr[tag][tmp].fa[j]].top]>k)
		res+=tr[tag][tmp].sum[j],tmp=tr[tag][tmp].fa[j];
	}
	res+=tr[tag][tmp].sum[0],tmp=tr[tag][tmp].fa[0];
	res-=tr[tag][tmp].dis,res+=tr[tag][tmp].sum[0];
	return sum[tag][k]+res;
}
int solve2(int x,int k,int tag){
	int tmp=x,res=tr[tag][x].dis-tr[tag][tmp].sum[0];
	for(int j=19;j>=0;--j){
		if(ord[tag][tr[tag][tr[tag][tmp].fa[j]].top]>k-1)
		res+=tr[tag][tmp].sum[j],tmp=tr[tag][tmp].fa[j];
	}
	res+=tr[tag][tmp].sum[0],tmp=tr[tag][tmp].fa[0];
	return sum[tag][k-1]+res;
}
int solve(int x,int k,int tag){
	k=min(k,len[tag]);
	if(ord[tag][tr[tag][x].top]<=k) return sum[tag][k];
	return max(solve1(x,k,tag),solve2(x,k,tag));
}
int main(){
	cin>>n>>q;
	for(int i=1;i<n;++i){
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		add(u,v,w,i<<1),add(v,u,w,i<<1|1);
	}
	find_diameter::main();
	dfs1(rt1,0),dfs2(rt1,0),dfs1(rt2,1),dfs2(rt2,1),init();
	for(int i=1;i<=q;++i){
		int x,y;scanf("%d%d",&x,&y),x=(x+lstans-1)%n+1,y=(y+lstans-1)%n+1;
		// printf("%d %d
",x,y);
		printf("%d
",lstans=max(solve(x,2*y-1,0),solve(x,2*y-1,1)));
	}
	return 0;
}
/*
应该是考虑以直径的两端点为根做长剖?
这样整棵树就被我们分成若干条长链,选取其中的最长的 2y-1 条即可?
但是考虑到这样选出来的连通块可能不包含 x ,这个是需要特判的。
考虑到我们是需要将 x 到根的部分选上的,直接考虑暴力跳长剖就行了吧(倍增)。
*/
原文地址:https://www.cnblogs.com/Point-King/p/15217195.html