CF911F Tree Destruction
前置知识
题意
一棵 个点的无根无权树,进行 次操作,每次进行如下操作:
- 选择树上的两个点 。
- 加上 之间的简单路径经过的边数。
- 删除点 或点 ,并输出删除过程。
求 的最大值。
分析
我们知道一个点在树上能最远达到的点必定是树的直径的端点,为了使答案最大,我们要使每个点的贡献最大,于是就可以先把不在直径上的点删去(要保证不影响其他点的最大贡献),再逐个删直径上的点。
我们就有了一个贪心策略:
- 求出树的直径长度 、两个端点 。
- 用两次 计算每个点到 的距离 。
- 答案 初始为 ,也就是最后要逐个删掉直径上的点,贡献为 。
- 通过比较 是否和 相等判断点 是否是直径上的点,并打上标记 。
- 对于所有不在直径上的点 , 加上 到 或 的最大距离 。这个过程可以和上一步一起实现。
- 输出 。
- 不断找出一个不在直径上的点直至没有,要保证这个点所连接的点到直径任意一端的路径不经过这个点,删除这个点并输出过程。可以这样理解:把直径 这条线举起来,让其他点沿地心引力自由下落,形成一颗颗子树,一个节点可以删除当且仅当它的子节点都被删除,
作者画图技术太烂请读者自行脑补。 - 选择直径的一端,从这一端开始删直径上的节点并输出过程。
求出 显然十分简单,但是求出删点过程却比较麻烦,这也正是这篇题解与其他题解的不同之处:如何求。
我们可以在输入加边时记录每个点所连的边数 ,对于每个不在直径上的点 ,令 自减,也就是减去它到父节点的一条边(树的形态就是贪心策略中所说的,只不过我们暂时不知道具体长什么样),此时一个没有子节点的 的 为 ,仿照拓扑排序的做法,将 的节点入队。
不断取出队头 ,输出 点的删除过程,把所有与 相连的点 的 减 ,使父节点 的 减少 ,如果 ,把 入队。
上述操作执行到队列为空,我们就删完了所有不在直径上的点。
Q: 为什么要把所有与 相连的点 的 减 ?这样不是会把子节点 的 重复减了吗?
A: 这样是使其父节点的边数减少,让其父亲成为一个新的叶子节点;子节点重复减不会有影响,因为这些节点的 之前已经为 ,再减 就变成负数,不会被入队。
处理好不在直径上的点后,我们从直径的一端开始 删点并输出过程即可。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=2e5+10;
int n,u,v,in[N];
vector<int>e[N];
bool vis[N];
#define T_D_T long long
struct Tree_Diameter{
int s,t;
T_D_T d[N],len;
void init(){
s=t=len=0;
memset(d,0,sizeof(d));
}
void dfs(int x,int fa){
for(int i=0;i<e[x].size();i++){
if(e[x][i]==fa)
continue;
d[e[x][i]]=d[x]+1;
if(d[e[x][i]]>d[s])
s=e[x][i];
dfs(e[x][i],x);
}
}
void calc(){
init();
dfs(1,0);
d[t=s]=0;
dfs(s,0);
len=d[s];
}
}tree;
ll D[2][N];
void dfs(int t,int x,int fa){
for(int i=0;i<e[x].size();i++){
if(e[x][i]==fa)
continue;
D[t][e[x][i]]=D[t][x]+1;
dfs(t,e[x][i],x);
}
}
void topu(){
queue<int>q;
for(int i=1;i<=n;i++)
if(!in[i])
q.push(i);
while(q.size()){
int x=q.front();q.pop();
printf("%d %d %d\n",(D[0][x]>D[1][x]?tree.s:tree.t),x,x);
for(int i=0;i<e[x].size();i++)
if(vis[e[x][i]]&&!(--in[e[x][i]]))
q.push(e[x][i]);
}
}
void dfs2(int x,int fa){
if(x==tree.t)
return;
printf("%d %d %d\n",tree.t,x,x);
for(int i=0;i<e[x].size();i++)
if(!vis[e[x][i]]&&e[x][i]!=fa)
dfs2(e[x][i],x);
}
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);
in[u]++;in[v]++;
}
tree.calc();
ll ans=(tree.len+1)*tree.len/2;
dfs(0,tree.s,0);dfs(1,tree.t,0);
for(int i=1;i<=n;i++)
if(D[0][i]+D[1][i]!=tree.len){
ans+=max(D[0][i],D[1][i]);
vis[i]=1;
in[i]--;
}
write(ans);cout<<endl;
topu();
dfs2(tree.s,0);
return 0;
}
时间复杂度 。

浙公网安备 33010602011771号