[CF980E]The Number Games——贪心+倍增
题目链接:
题目大意:
给出一棵$n$个节点的树,第$i$个节点权值为$2^i$,要求删除$k$个点且保证剩下点联通情况下权值和最大。
首先可以想到一定先选大的,那么节点$n$必须选,剩下的从大到小依次选。
因为需要保证联通,我们不妨以$n$为根,如果要选$i$节点,那么从$i$到根的所有点都要选。
对于每个点是否能选只需判断从它到根需要选的节点数是否小于等于当前剩余可选节点数即可。
朴素做法可以从点$i$往上一步一步爬,当遇到第一个已经被选的节点时,说明上面的节点都被选了,求出这段路径节点数即可。
但这样时间复杂度会被卡成$O(n^2)$,所以我们改成倍增往上爬,时间复杂度为$O(nlogn)$。
每选一个节点时一步一步往上爬并标记即可。
因为每个点只会被标记一次,所以时间复杂度为$O(n)$。
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lowbit(x) (x&(-x))
#define pr pair<int,int>
using namespace std;
int n,k;
int head[1000010];
int nex[2000010];
int to[2000010];
int f[1000010][20];
int vis[1000010];
int x,y;
int tot;
int res;
int dep[1000010];
void add(int x,int y)
{
nex[++tot]=head[x];
head[x]=tot;
to[tot]=y;
}
void dfs(int x,int fa)
{
dep[x]=dep[fa]+1;
f[x][0]=fa;
for(int i=1;i<=19;i++)
{
f[x][i]=f[f[x][i-1]][i-1];
}
for(int i=head[x];i;i=nex[i])
{
if(to[i]!=fa)
{
dfs(to[i],x);
}
}
}
int query(int x)
{
if(vis[x])return 0;
int now=x;
for(int i=19;i>=0;i--)
{
if(!vis[f[x][i]]&&f[x][i])
{
x=f[x][i];
}
}
return dep[now]-dep[x]+1;
}
void change(int x)
{
while(!vis[x])
{
vis[x]=1;
res++;
x=f[x][0];
}
}
int main()
{
scanf("%d%d",&n,&k);
k=n-k;
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(n,0);
k--;
vis[n]=1;
res++;
for(int i=n-1;i>=1;i--)
{
if(k==0)break;
int sum=query(i);
if(sum<=k)
{
k-=sum;
change(i);
}
}
res=n-res;
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
printf("%d",i);
res--;
if(res)printf(" ");
}
}
}

浙公网安备 33010602011771号