980E. The Number Games(倍增,思維)
題目鏈接:傳送門
思路:
我們轉化爲,從一顆樹上選n-k個點,使得貢獻最大,且這n-k個點兩兩連通。貪心的取,我們必定先取大的(因爲如果可以取大的但不取必虧)。
我們可以將原圖變爲以n爲根的有根樹,首先n號點必選,我們接下來探討下面選點,我們建立倍增數組,代表u的第個祖先的編號,我們看編號爲的點。
如果編號爲到編號爲 的點的數量小於剩下可選的點的數量,那就不能選。
否則,我們選號點,爲了連通性, 號點到號點之間的點都要選。
我們將選的點進行標記。
所以我們有一個算法:
先選編號爲n的點,然後遍歷剩下編號爲的點(從大到小),假設此時編號爲,如果編號爲的點已經被選,則跳過,否則倍增找祖先沒有被標記的最小的近的祖先,(爲什麼是祖先呢?,因爲被標記的不可能是 i 的兒子,否則他就被選了)那麼找最近被標記的點就可以用倍增。
如果之間點的數量小於可選的點的數量,就不選。否則就選,並選其之間的點。
#include<bits/stdc++.h>
#define mse(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int N=1e6+10;
vector<int> g[N];
int book[N];
int fa[N][20];
void bfs(int root)
{
queue<int> qe;
qe.push(root);
fa[root][0]=root;
while(!qe.empty())
{
int u=qe.front();qe.pop();
for(int i=1;i<20;++i) fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:g[u])
{
if(v==fa[u][0]) continue;
fa[v][0]=u;
qe.push(v);
}
}
}
int mincost(int v)
{
int ans=0;
if(book[fa[v][0]]) return 1;
int wv=v;
for(int i=19;i>=0;--i)
{
if(book[fa[wv][i]]) continue;
ans+=1<<i;
wv=fa[wv][i];
}
return ans+1;
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);
cout.tie(0);
int n,k;
cin>>n>>k;
for(int i = 1; i < n; ++i)
{
int u,v;
cin>>u>>v;;
g[u].push_back(v);
g[v].push_back(u);
}
k=n-k-1;
bfs(n);
book[n]=1;
for(int i=n-1;i>=1&&k;--i)
{
if(book[i]) continue;
int m=mincost(i);
if(m<=k)
{
k-=m;
int wv=i;
while(!book[wv]){
book[wv]=1;
wv=fa[wv][0];
}
}
}
for(int i=1;i<=n;++i)
{
if(!book[i])
cout<<i<<" ";
}
cout<<endl;
return 0;
}