Codeforces 526G - Spiders Evil Plan(长链剖分+直径+找性质)

Codeforces 题目传送门 & 洛谷题目传送门

%%%%% 这题也太神了吧 storz 57072 %%%%%

首先容易注意到我们选择的这 \(y\) 条路径的端点一定是叶子节点,否则我们总可以将其调整到叶子节点并使答案不会更劣,并且如果非必须(\(2y\le\) 树中叶子节点的个数),我们选择的这 \(y\) 个路径的 \(2y\) 个端点一定两两不相同,否则我们还是可以调整重复的叶子节点的位置使答案不变劣。

其次我们还可以发现,对于固定的 \(2y\) 个叶子节点,我们总存在一种选法使得这 \(y\) 个路径的并集就是这 \(2y\) 个节点的虚树,证明应该不难,大概归纳一下就行了?这里就不再赘述。特判掉 \(2y>\) 树在叶子节点个数的情况,题目就等价于求一个包含 \(x\) 的含 \(2y\) 个叶子节点的虚树,使得虚树中边权之和尽可能大。我们考虑以 \(x\) 为根,先不考虑“虚树必须包含 \(x\)”这个条件,即我们假设要求 \(x\) 与这 \(2y\) 个叶子节点共同的虚树,显然这个集合就等于这 \(2y\) 个叶子节点到根节点路径的并集,也就是说我们要选 \(2y\) 个叶子节点使得它们到根节点路径的并集中边权和尽可能大。这似乎看起来有些熟悉?……不就 BZOJ3252 攻略吗?直接长链剖分一遍排个序即可。只不过这边要特判掉 \(x\) 本身就是叶子节点的情况,因为显然 \(x\) 必须被选入这 \(2y\) 个叶子节点中,故我们还需额外选出 \(2y-1\) 个叶子节点 instead of \(2y\) 个叶子节点。加上“虚树必须包含 \(x\)”这个条件也不困难,不难发现出问题的情况只有一种可能,那就是 \(x\) 本身不是叶子节点,并且选择的 \(2y\) 的叶子节点全在 \(x\) 的同一子树中,那根据贪心的思想,我们肯定会牺牲权值最小的长链,并加入叶子节点属于 \(x\) 的其他子树中权值最大的长链,这个随便搞搞就行了,至此我们解决了单词询问的情况。

接下来考虑多组询问的情况,不难发现上述算法的瓶颈在于每次询问都要重新以 \(x\) 为根搞一遍长链剖分,而长链剖分这类数据结构也不支持可持久化什么的,因此该算法也无法直接优化。那有什么办法呢?不妨来找找性质罢,不难发现在选择的 \(2y\) 个叶节点中,总有一个最特殊的节点——那就是离 \(x\) 最远的节点,因为这个节点不论怎样也不可能被删除(每次最多删除一个节点,而我们选择的节点数 \(\ge 2\))。而根据树的直径的性质,该点一定是树的直径的一个端点,因此我们考虑着眼于树的直径上,我们对于树的直径的两个端点 \(u,v\) 分别询问以下事情:求选择 \(2y\) 个叶子节点,其中一个是 \(u\)(或 \(v\)),并且它们的虚树包含 \(x\),它们虚树边权之和的最大值。显然我们只需在两个方案中取个较大值即可。那么这个最大值怎么求呢?不妨以包含 \(u\) 的部分举例,我们还是考虑以 \(u\) 为根,显然对于固定的另外 \(2y-1\) 个叶子节点的集合 \(\{x_1,x_2,\cdots,x_{2k-1}\}\),它们与 \(u\) 的虚树也一定是这 \(2k-1\) 个节点到 \(u\) 路径的并集。如果不考虑“必须包含 \(x\)”这个条件,那依旧一遍长链剖分就可以搞定。加上这个条件怎么办呢?还是考虑先按照不必须包含 \(x\) 的方案来选择叶子节点,然后在此基础上进行调整,记 \(rk_x\) 为对于 \(x\) 节点所在长链,其链上所有边权值之和在所有长链中从大到小排名是多少。如果 \(rk_x\le 2y-1\),那 \(x\) 已经被包含入这 \(2y\) 个叶子节点的虚树中了,就不用再调整了,否则调整方法只可能有以下两种:

  1. 删去已经选择的 \(2y-1\) 个长链中,权值最小的长链(设其权值为 \(val\)),并打通 \(x\) 子树内深度最大的叶子节点 \(w\) 到根节点的路径,设 \(x\) 到根节点路径上第一个 \(rk\le 2y-2\) 的节点为 \(z\),那显然打通这条路径权值的增量为 \(dis_{w}-dis_z\),其中 \(dis_i\) 为根节点到 \(i\) 路径上权值之和,总增量为 \(\Delta=-val+dis_w-dis_z\)
  2. 记设 \(x\) 到根节点路径上第一个 \(rk\le 2y-1\) 的节点为 \(t\),如果 \(t\) 子树中只有一个叶节点被选择,那么我们可以索性把 \(t\) 子树内原来选择的边直接去掉,取而代之的是连接 \(t\)\(x\) 子树内深度最大的节点 \(w\) 的路径上的边,这部分的增量为 \(\Delta=-mxdep_t+mxdep_x\)

求一个点到根节点上第一个 \(rk\le t\) 的点可以用倍增,总复杂度 \(n\log n\),因为有个倍增的 \(\log\)

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
	#define FILE_SIZE 1<<23
	char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
	inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
	inline void putc(char x){(*p3++=x);}
	template<typename T> void read(T &x){
		x=0;char c=getchar();T neg=0;
		while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
		while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
		if(neg) x=(~x)+1;
	}
	template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
	template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
	void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
const int LOG_N=17;
int n,qu,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],val[MAXN*2+5],ec;
void adde(int u,int v,int w){to[++ec]=v;val[ec]=w;nxt[ec]=hd[u];hd[u]=ec;}
namespace dia{
	int dis1[MAXN+5],dis2[MAXN+5],rt1=1,rt2=1;
	void dfs1(int x,int f){
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e],z=val[e];if(y==f) continue;
			dis1[y]=dis1[x]+z;dfs1(y,x);
		}
	}
	void dfs2(int x,int f){
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e],z=val[e];if(y==f) continue;
			dis2[y]=dis2[x]+z;dfs2(y,x);
		}
	}
	void find_dia(){
		dfs1(1,0);for(int i=1;i<=n;i++) if(dis1[i]>dis1[rt1]) rt1=i;
		dfs2(rt1,0);for(int i=1;i<=n;i++) if(dis2[i]>dis2[rt2]) rt2=i;
	}
}
struct solver{
	int rt,dep[MAXN+5],mxdep[MAXN+5],dson[MAXN+5],top[MAXN+5];
	int fa[MAXN+5][LOG_N+2],rnk[MAXN+5];
	pii chain[MAXN+5];int chain_n=0,sum[MAXN+5];
	void dfs0(int x,int f){
		fa[x][0]=f;
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e],z=val[e];if(y==f) continue;
			dep[y]=mxdep[y]=dep[x]+z;dfs0(y,x);
			if(mxdep[y]>mxdep[x]) mxdep[x]=mxdep[y],dson[x]=y;
		}
	}
	void dfs1(int x,int tp){
		top[x]=tp;if(dson[x]) dfs1(dson[x],tp);
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e];if(y==fa[x][0]||y==dson[x]) continue;
			dfs1(y,y);
		}
	}
	void init(){
		dfs0(rt,0);dfs1(rt,rt);
		for(int i=1;i<=LOG_N;i++) for(int j=1;j<=n;j++)
			fa[j][i]=fa[fa[j][i-1]][i-1];
		for(int i=1;i<=n;i++) if(top[i]==i)
			chain[++chain_n]=mp(mxdep[i]-dep[fa[i][0]],i);
		sort(chain+1,chain+chain_n+1);reverse(chain+1,chain+chain_n+1);
//		for(int i=1;i<=chain_n;i++) printf("%d %d\n",chain[i].fi,chain[i].se);
		for(int i=1;i<=chain_n;i++) rnk[chain[i].se]=i;
		for(int i=1;i<=n;i++) rnk[i]=rnk[top[i]];
		for(int i=1;i<=chain_n;i++) sum[i]=sum[i-1]+chain[i].fi;
	}
	int getfst(int x,int rk){
		for(int i=LOG_N;~i;i--) if(rnk[fa[x][i]]>rk) x=fa[x][i];
		return fa[x][0];
	}
	int query(int x,int y){
		y=(y<<1)-1;if(y>chain_n) return sum[chain_n];
		if(rnk[x]<=y) return sum[y];int anc=getfst(x,y);
//		printf("anc=%d\n",anc);
		return max(sum[y-1]+mxdep[x]-dep[getfst(x,y-1)],sum[y]-mxdep[anc]+mxdep[x]);
	}
} t[2];
int main(){
	scanf("%d%d",&n,&qu);
	for(int i=1,u,v,w;i<n;i++){scanf("%d%d%d",&u,&v,&w);adde(u,v,w);adde(v,u,w);}
	dia::find_dia();t[0].rt=dia::rt1;t[1].rt=dia::rt2;t[0].init();t[1].init();
	int ans=0;while(qu--){
		int x,y;scanf("%d%d",&x,&y);x=(x+ans-1)%n+1;y=(y+ans-1)%n+1;
//		printf("real %d %d\n",x,y);
		printf("%d\n",ans=max(t[0].query(x,y),t[1].query(x,y)));
	}
	return 0;
}
posted @ 2021-03-25 18:01  tzc_wk  阅读(76)  评论(0编辑  收藏  举报