CF516D. Drazil and Morning Exercise

题目传送门

比较简单的题,不过有很多有趣的东西。

题目大意是说,一棵树 \(n\) 个点,边有边权(\(1\le v \le 10^6\),为正整数),然后定义 \(f_i\) 表示 \(i\) 到所有叶子结点(定义为度数为 \(1\) 的点)的最大值。

然后要选出一个最大的联通快,使得 \(max f_i -min f_i \le l\)\(q\) 次询问 \(q\le50\)

一个显然的,我们可以换根 dp 求出 \(f_I\),然后既然给了 \(l\),我们考虑枚举最小值,然后动态维护(具体的就是枚举 \(i\),然后看 \(i\le f_j \le i+l\) 的点构成的联通快最大为多少),然后套上 lct 应该就可以做到 \(nq\log\),感觉不太好过的样子。

这个 f 的计算方式其实明眼人看着就知道很蹊跷吧,考虑看一下 \(f_i\) 的取值,接下来也许会说一些无用的话,随便看看就行了,定义 \(dis_{i,j}\) 表示 \(i,j\) 的最短距离。

对于 \(f_i\) 来讲,若其父亲(假设有)选的点不是 \(i\) 子树类的,那么显然 \(i\) 也会取那个点(因为对于 \(fa_i\) 来讲,不取说明 \(dis_{i,fa_i}+dis_{i,son_i} \le dis_{fa_i,k}\),那么对于 \(i\) 来讲就是 \(dis_{i,son_i}\)\(dis_{fa_i,k}+dis_{i,fa_i}\),显然后者更好)。

所以对于根节点跑出来最优点 \(k\) 之后,我们直接前往 \(root\)\(k\) 这条链去看。

如果 \(i\) 也是到 \(k\) 最优,那么继续去看,其它子树都这么赋。

否则,\(i\) 一定是到一个子树外更优的,不然 \(k\) 就会变。那么 \(i\) 子树里面都是到那个点了那么结束。

综上,所有点的只有可能到两个点中的一个去(其实就是直径的两个端点)。

嗯,然后我们可以直接去求重心(即 \(f_i\) 最小的点),然后以重心为更节点,容易发现,要么所有点都会到一个叶子去,要么重心周围的点恰好有一个是与重心到的点是不一样的。

很有趣对吧,然后这么跑出来就显然会有 \(f_{fa_i} \le f_I\) 了(不然凭啥 \(i\) 不当重心,这不纯纯黑幕)。

所以是一个小根堆,我们从大到小枚举最大值,然后删最大值(记得删的时候优先删深度更大的),直接并查集维护即可,复杂度 \(nq*c\)\(c\) 是路径压缩那个数,不会打(

也许有点关联的题:

P3345 [ZJOI2015] 幻想乡战略游戏

code

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
	template<typename T>
	void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
	template<typename T,typename... Args>
	void read(T &_x,Args&...others){Read(_x);Read(others...);}
	const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
	#define pc(x) buf[plen++]=x
	#define flush(); fwrite(buf,1,plen,stdout),plen=0;
	template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 1e5+10;
int n,q,x,y,z,head[N],cnt,F[N],f[N],fa[N],g[N],siz[N],r,dep[N],o,ly,ans;
//f_i表示子树到i的最大值,g_i是全局 
struct w
{
	int to,nxt,z;
}b[N<<1];
struct w1
{
	int z,x;
}a[N];
inline void add(int x,int y,int z)
{
	b[++cnt].nxt = head[x];
	b[cnt].to = y; b[cnt].z = z;
	head[x] = cnt;
}
void dfs(int x,int y)
{
	siz[x] = 1;
	for(int i = head[x];i;i = b[i].nxt)
		if(b[i].to != y)
			dfs(b[i].to,x),siz[x] += siz[b[i].to],f[x] = max(f[x],f[b[i].to]+b[i].z);
}
void dfs1(int x,int y,int z)//求出树的重心 
{
	int mx = 0,cmx = 0; g[x] = max(f[x],z);
	if(g[x] < ly) ly = g[x],o = x;
	for(int i = head[x];i;i = b[i].nxt)
		if(b[i].to != y)
		{
			if(f[b[i].to]+b[i].z > mx) cmx = mx,mx = f[b[i].to]+b[i].z;
			else if(f[b[i].to]+b[i].z > cmx) cmx = f[b[i].to]+b[i].z;
		}
	for(int i = head[x];i;i = b[i].nxt)
		if(b[i].to != y)
		{
			if(f[b[i].to]+b[i].z == mx) dfs1(b[i].to,x,max(z,cmx)+b[i].z);
			else dfs1(b[i].to,x,max(z,mx)+b[i].z);
		}
}
void dfs2(int x,int y)
{
	dep[x] = y+1; fa[x] = y;
	for(int i = head[x];i;i = b[i].nxt)
		if(b[i].to != y)
			dfs2(b[i].to,x);
}
int find(int x)
{
	if(F[x] == x) return x;
	return F[x] = find(F[x]);
}
inline bool cmp(w1 x,w1 y)
{ 
	if(x.z == y.z) return dep[x.x] > dep[y.x]; 
	return x.z > y.z; 
}//值相同优先跑深度大的 
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	read(n); ly = 1e18;
	for(int i = 1;i < n;i++) read(x),read(y),read(z),add(x,y,z),add(y,x,z);
	dfs(1,0); dfs1(1,0,0); dfs2(o,0);
	for(int i = 1;i <= n;i++) a[i].z = g[i],a[i].x = i;
	sort(a+1,a+1+n,cmp); read(q);
	for(int i = 1;i <= q;i++)
	{
		read(x); r = 1; ans = 0;
		for(int j = 1;j <= n;j++) F[j] = j,siz[j] = 1;
		for(int j = 1;j <= n;j++) 
		{
			while(r <= n && a[j].z-x <= a[r].z) 
			{
				for(int z = head[a[r].x];z;z = b[z].nxt)
					if(b[z].to != fa[a[r].x])//从大到小,直接这么取即可 
					{
						F[b[z].to] = a[r].x;
						siz[a[r].x] += siz[b[z].to];
					}
				ans = max(ans,siz[a[r].x]);
				r++;
			}
			siz[find(a[j].x)]--;
		}
		print(ans),pc('\n');
	}
	flush();
	/*
	(x-l,x) 
	我们从大到小枚举,这样删除都是删除深度最大值
	即枚举最大值 
	*/ 
	return 0;
}
/*
重心到外面是最小的,原因显然(不是看子树最小的重心,而是看最大值最小的重心) 
通过换根dp很容易得到每个点走到叶子的最大花费(注意判断一下根节点也是叶子的情况)
问题变成求最大的联通快使得max-min<=l 
枚举最小值,最大值也确定了
在这之间的都能选了
那么总数量是O(n)的,可以暴力加入删除
考虑每次删除f_i=x的,加入f_i=x+l的(l只有50种,每个暴力看)
然后可以套lct了,但我不会(
猜测f肯定是具有什么性质的(不然为啥不直接给你捏) 
如果i到一个子树外的点最优,那么其子树一定都是到这里最优 
我去好像有用的点最多两个
因为假设i要取子树外的,而i父亲不取
那么到那边去,如果那个点是到其子树内那么也取这个
否则,对于那个点肯定是到fa_i要到的地方去
那么最多两种点,其实就是直径
看了题解确实是,直接从重心开始,这样就是f_i>=f_{fa_i}
这个性质非常好,而且也十分显然,不然不满足重心的性质了
推荐题解:https://www.luogu.com.cn/article/on75r4g3
*/
posted @ 2025-10-22 22:48  kkxacj  阅读(3)  评论(0)    收藏  举报