题目大意 : 有一棵点数为 n 的数, 第 ii 个点的点权是 2i 你需要删掉 k 个点, 使得删掉这些点后树依然联通, 且剩下的点权之和最大, 并输出方案
n,k1000000
解题思路 :
该问题可以转化为选取 n?k 个点, 使得选取的点联通且权值和最大
根据点权是 2i, 有选取编号为 x 的点比选取 i=[1,x) 之间的所有点还要优 (手推一下就知道啦)
首先 n 一定要保留, 于是可以将 n 设置为 root 把无根树变成有根树来简化问题
接下来不妨贪心的从大到小枚举点, 对于当前的点, 判断是否已经被标记, 如果没有被标记, 那么就找到离当前点最远的没有被标记的祖先,
如果要标记这个点, 那么总共增加的点就是从当前点到离当前点最远的没有被标记的祖先的路径上的所有点的数量, 也可以解释为当前点到已经保留的点所构成的树的路径上的点.
所以对于一个点 x 只需要倍增找到其到 root 路径上最深的未被选取的点 y
那么路径上没有被选取的点的个数就是 dep[x]-dep[y]+1 如果可以选取就 dfs 枚举这些点并标记为取.
因为每个点只会被最多选取一次, 总复杂度是 O(nlogn)
- var
- a,next,head,dep:array[0..2000005]of longint;
- f:array[0..1000005,0..24]of longint;
- vis:array[0..2000005]of boolean;
- i,n,m,k,e,root,u,v,fi,j,x,y:longint;
- procedure add(x,y:longint);
- begin
- inc(e);a[e]:=y;next[e]:=head[x];head[x]:=e;
- end;
- procedure dfs(x:longint);// 预处理深度
- var i,v:longint;
- begin
- i:=head[x];
- while i>0 do
- begin
- v:=a[i];
- if dep[v]=0 then
- begin
- dep[v]:=dep[x]+1;
- f[v,0]:=x;
- dfs(v);
- end;
- i:=next[i];
- end;
- end;
- function get(x:longint):longint;// 找最远的未被访问的点, 倍增
- var i:longint;
- begin
- for i:=22 downto 0 do
- begin
- if (not(vis[f[x,i]]))and(f[x,i]<>0)then x:=f[x,i];
- end;
- exit(x);
- end;
- procedure redfs(u,x:longint);// 向上标记点
- var i:longint;
- begin
- vis[u]:=true;
- if u=x then exit;
- redfs(f[u,0],x);
- end;
- begin
- readln(n,k);k:=n-k-1;// 节点 n 占掉一个位子
- for i:=1 to n-1 do
- begin
- readln(x,y);
- add(x,y);
- add(y,x);
- end;
- root:=n;
- f[root,0]:=root;// 预处理
- dep[root]:=1;
- dfs(root);
- for i:=1 to 22 do
- for j:=1 to n do
- begin
- f[j,i]:=f[f[j,i-1],i-1];// 倍增初始化
- end;
- vis[n]:=true;
- for i:=n-1 downto 1 do
- if not vis[i] then
- begin
- u:=i;fi:=get(u);
- if dep[u]-dep[fi]+1<=k then
- begin
- k:=k-(dep[u]-dep[fi]+1);
- // writeln(k);
- redfs(u,fi);
- end;
- end;
- for i:=1 to n do if not vis[i] then write(i,' ');
- end.
来源: http://www.bubuko.com/infodetail-2703194.html