题目链接
http://codeforces.com/problemset/problem/980/E
题意简述
给你n个点,n<=100w,每个点有点权,点权为
2i
2
i
,再给你一个整数k,k<=100w,表示你要删除k个点后使得这棵树仍联通且剩余点的权值和最大,输出所有删除的点。
乍一看以为树形dp,突然发现k也小于等于100w,好像不行啊。
我们把题意转换为选择n-k个点,这些点联通且权值和最大。
猛然发现一个非常特殊的性质,第i个点的点权为
2i
2
i
,nice啊,这就保证了我们肯定不惜任何代价要将目前最大的点连进来。
所以我们用一个set维护现在还未选择的点,找到当前最大的点,用倍增判一下选择它最少要选择多少个点,(具体实现看代码),如果是小于等于现在还能选的点数res的,那就从他开始暴力往上跳(建树的时候以n为根),把沿路经过的定点都从set里删除,顺便把vis标记一下。如果不够选择,把这个点从set里删掉,继续做就行了。
最后vis没有被标记过的点就是没有选择的点了。
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<iostream>
#include<cmath>
#include<set>
#define LL long long
#define INF (2139062143)
#define N (1000001)
using namespace std;
int n,k,root,x,y,tot,res;
int fa[N][31],a[N<<1],nxt[N<<1],head[N];
bool vis[N];
set <int> S;
set <int>::iterator Pos;
inline void add(int x,int y){
a[++tot]=y,nxt[tot]=head[x],head[x]=tot;
}
template <typename T> void read(T&t) {
t=0;
bool fl=true;
char p=getchar();
while (!isdigit(p)) {
if (p=='-') fl=false;
p=getchar();
}
do {
(t*=10)+=p-48;p=getchar();
}while (isdigit(p));
if (!fl) t=-t;
}
void dfs(int u){
for (int p=head[u];~p;p=nxt[p]){
if (a[p]!=fa[u][0]){
fa[a[p]][0]=u;
dfs(a[p]);
}
}
}
int main(){
read(n),read(k);
memset(head,-1,sizeof(head));
for (int i=1;i<n;i++){
S.insert(i);
read(x),read(y);
add(x,y);
add(y,x);
}
dfs(n);
for (int j=1;j<=30;j++){
for (int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
if (k==n){
for (int i=1;i<=n;i++) printf("%d ",i);
return 0;
}
vis[n]=1;
vis[0]=1;
res=n-k-1;
while (res>0){
Pos=S.end();
Pos--;
int k=*Pos,tt=0,now=k;
for (int i=30;i>=0;i--){
if (!vis[fa[now][i]]){
now=fa[now][i];
tt+=1<<i;
}
}
tt++;
//printf("%d %d\n",k,tt);
if (tt<=res){
int now=k;
while (!vis[now]){
vis[now]=1;
res--;
S.erase(now);
now=fa[now][0];
}
}
else{
S.erase(Pos);
}
}
for (int i=1;i<=n;i++){
if (!vis[i]) printf("%d ",i);
}
return 0;
}