題面

題意
給定 n 個節點 n-1 條邊組成的樹,以節點 1 為根
現需要選出 k 個節點作為工業城市,其余城市均為旅游城市
問從所有工業城市出發走到根節點所經過的旅游城市數量之和的最大值
解題思路
反向思考,假定整個圖全都是工業城市,那我們就需要選出 n-k 個旅游城市即可
既然以節點 1 為樹根,那么對於每個節點,我們可以計算出以這個節點為根的子樹中包含的節點數量以及這個節點擁有的祖先節點數量
可以發現,根據貪心,我們選擇的旅游城市都是盡可能靠近根節點的
毫無疑問,第一步肯定是將根節點選成旅游城市,那么此時我們的答案就是 n-1,即此時工業城市的數量
以樣例一的圖為例

第一步選擇 1 為旅游城市后,答案為 6 ,即工業城市數量
那么第二步——
如果我們選擇 3 作為旅游城市,可以發現在節點 5 和節點 6 的路徑上都增加了一個旅游城市,所以此時的答案為 6-1+2=7
如果我們選擇 4 作為旅游城市,可以發現在節點 7 的路徑上增加了一個旅游城市,所以此時的答案為 6-1+1=6
其余的,如果選擇 2 5 6 7 作為旅游城市,則並不會有其余任意一個工業城市會經過這些旅游城市,答案會變成 6-1+0=5
綜上,如果接下來選擇某個節點 i 作為旅游城市,
那么對答案的“增加的貢獻”就是以節點 i 為根的子樹(不包括節點 i )包含的節點數量
對答案的“減少的貢獻”就是節點 i 的所有祖先節點中的旅游城市數量
又因為貪心可知,我們要么增加(增加的貢獻),要么減少(減少的貢獻),才能答案的值取到最大
且越靠近根節點的節點,子節點的數量又多,祖先節點的數量又少,肯定會優先取得
所以每個節點對答案的貢獻就可以轉化成 以節點 i 為根的子樹(不包括節點 i )包含的節點數量 - 節點 i 的祖先節點數量
處理出來后排序,再從大開始取 n-k 個即可
完整程序
設根節點 1 的深度為 0
那么每個節點的祖先節點數量也就成為了每個節點的深度
對於以節點 i 為根的子樹(不包括節點 i )包含的節點數量,dfs遞歸求和即可
(Pretests: 264ms/2000ms)
(System Tests: 327ms/2000ms)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int> G[200050];
bool vis[200050];
struct node
{
int cnt,dis; //cnt存子樹除該節點外的節點數量,dis存節點深度
bool operator < (const node& a) const
{
return cnt-dis>a.cnt-a.dis; //按照對答案的貢獻(cnt-dis)從大到小排序
}
}ar[200050];
int dfs(int pos,int fa)
{
vis[pos]=true; //標記訪問
ar[pos].dis=ar[fa].dis+1; //節點深度=父節點節點深度+1
for(int it:G[pos])
if(!vis[it])
ar[pos].cnt+=dfs(it,pos); //求除自己以外子樹中節點數量之和
return ar[pos].cnt+1; //返回時要加上自己
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n,k,a,b;
cin>>n>>k;
for(int i=1;i<n;i++)
{
cin>>a>>b;
G[a].push_back(b);
G[b].push_back(a);
}
ar[0].dis=-1; //特殊處理下,dis[1]=dis[0]+1=0 -> dis[0]=-1
dfs(1,0);
sort(ar+1,ar+n+1);
ll ans=0;
for(int i=1;i<=n-k;i++)
ans+=ar[i].cnt-ar[i].dis;
cout<<ans<<'\n';
return 0;
}
