CF1842F Tenzing and Tree 题解

Tenzing and Tree

感觉很典型的题,就是树的重心+绝对值等式

解法:

以每个点 \(i\) 为根分别 \(bfs\) ,得到一个距离数组 \(dis\) ,取前 \(k\) 个值的权值为和,更新 \(w[k]\) 的值, \(n\) 个点分别为根,更新 \(n\) 遍之后,得到 \(w\) 数组,则 \((n-1)\times i-w[i]\) ,即为 \(i\) 个点时候的答案

下面证明一下正确性:

1、

首先,证明最优解下点一定是联通的,我们可以假设一开始的时候,在树的一个叶子节点上,放了 \(k\) 个黑点,也就是假设一开始没有一个节点上只能放一一个黑点的限制,后续通过我们不断平移, 一次将一个黑点平移到相邻边,使得最终 \(k\) 个黑点分摊在 \(k\) 个树上的真实节点上。

那一开始的时候,总贡献即为 \((n-1)* k\) ,当第一次平移一个点的时候,有一条边的贡献发生了变化,不断的平移,总贡献和就不断的减少。

那假设黑点不是联通的,不妨考虑只差-步的情形,一侧 \(k-1\) 个点是联通的,另-侧1个黑点,且两个连通块之间隔着一个白点,那此时有两条边,满足一侧为 \(n-1\)个黑点,另- -侧为1个点。

如果 \(1\) 个黑点占据了那个白点的位置,则可以使得只有一边,满足一侧为 \(n-1\) 个黑点,另一侧为 \(1\) 个黑点,而刚才的另一条边,变为一侧有 \(n\) 个黑点的情况,即总贡献 \(+2\)

通过增量法,我们可以使得差 \(x\) 步可以优化到差 \(x-1\) 步,只差一步 最终优化到联通。

2、

其欺,证明一下刚才的求法能求得最优解, \(w\) 数组对应的最小值,由于是 \(bfs\) 的( \(dfs\) 也可以) ,所以可以还原回树上的黑点连通块,固定根为 \(i\) ,求得的 \(dis\) 数组,选得前 \(k\) 个求和,代表选了 \(k\) 个点。

既可以理解为k个黑点到根的距离和, \(dis=x\) 表示每个点上方有 \(x\) 个点,或者有 \(x\) 条边,也可以理解为所有边对应的子树中的点的数量和,即交换求和顺序,从边的视角来看这个和式。

\[(n-1)* k - \sum dis* 2 \]

\[=(n-k)* k+k* (k-1) -\sum dis* 2 \]

\[=(n-k)* k+ \sum (k) - dis* 2 \]

\[=(n-k)* k+ \sum (k-dis)-dis \]

答案的式子,在求 \(k\) 个黑点的最优解时,

\(\bullet\) \(1.\)\(n-k\) 条边,满足 \(k\) 个点在这些边的一侧,其代价为 \((n-k)*k\)

\(\bullet\) \(2.\) 对于两侧都有黑点的这\(k\) 条边来说,后面和式表示非子树点数量和子树点数量和。

而这距离最终的答案还有一个绝对值。

如果是 \((n- k)* k + \sum abs((ki - dis) - dis)\) ,就是我们想要的答案了。

当枚举 \(n\) 个根时,每个根都对应一个局部最优解, \(n\) 个局部最优解的最大值,是最终解,最终解得到的连通块,还是一棵树,不妨称其为最优点树(不唯一) , 而树一定有重心,我们一定可以通过最优黑点树的重心 \(Q\)为根,枚举到某一棵最优黑点树。

此时,根据重心性质,子树大小不超过树总大小的一半,即 \(dis \le k/2\) ,使得 \((k- dis)- dis\) 一定非负,和式等于 \(\sum abs((k - dis) - dis)\)

那么其他非最优解的情况,其值只会多减,导致比 \(abs\) 求和小,不会影响最优解。

有类似曼哈顿距离 \(Q\) 的松弛,绝对等式 \(abs(x-y)=max(x+y-2y,x+y-2x)\) ,只要最优解下能和 \(abs\) 取等,其他情况下,算的不是 \(abs\) 也无妨。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=5e3+100;
int n,u,v;
int dis[N],w[N];
vector<int> e[N];
inline int read(){
	char c=getchar();int f=1,x=0;
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
void bfs(int u){
	for(int i=1;i<=n;i++) dis[i]=n+1;
	queue<int> q;
	q.push(u);
	dis[u]=0;
	int num=0,sum=0;
	while(!q.empty()){
		u=q.front();
		q.pop();
		num++;
		sum+=dis[u];
		w[num]=min(w[num],sum);
		for(int i=0;i<e[u].size();i++){
			int v=e[u][i];
			if(dis[v]>dis[u]+1){
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
} 
int main(){
	n=read();
	for(int i=1;i<n;i++){
		u=read();
		v=read();
		e[u].push_back(v);
		e[v].push_back(u);
	}
	memset(w,INF,sizeof(w));
	for(int i=1;i<=n;i++) bfs(i);
	printf("0 ");
	for(int i=1;i<=n;i++) printf("%d ",(n-1)*i-2*w[i]);
	return 0;
}
posted @ 2023-09-22 22:14  xuantianhao  阅读(20)  评论(0)    收藏  举报