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