題意:一棵大小爲n的有根樹上,要求將K個節點設爲工業城市,其餘爲旅遊城市。只有工業城市對總收益有貢獻且每個工業城市的貢獻爲從它到根的路徑上旅遊節點的個數。求合理安置K個工業城市後最大化的收益。
題解:
這就是傳說中的“正難則反”嗎...
如果考慮貪心地安放工業城市,那安排完最深的點之後,先安排其他子樹中較深的點?還是安排當前子樹中次深的點?這個似乎無法確定。
所以先默認所有點是工業城市,嘗試貪心地安放旅遊城市。引理:如果x點是旅遊城市,那麼它的父節點一定是旅遊城市。否則兩者交換一下一定更優。所以我們嘗試從淺往深安排旅遊城市。每次新安排一個旅遊城市x,由於其子樹目前一定是工業城市,所以增加的收益是size[x]-1,由於自身作爲工業城市的貢獻需要減去,所以減少的收益是depth[x]-1。於是安排x造成的貢獻是depth[x]-size[x]。所以把所有點按照depth[x]-size[x]從大到小排序,然後把前n-K大的標記爲旅遊城市,後面再dfs一遍算一下工業城市的貢獻就好。(其實可以直接累加前n-K項depth[x]-size[x]得到答案,相當於是算每一個旅遊城市的貢獻)(看來思維還沒有完全反過來,自己做時還是算的工業城市的貢獻......)
感覺這個題能這麼做還是挺湊巧的,按上述方式排序後確實可以保證先安排旅遊的是淺的點,所以這麼貪心確實是對的。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=2e5+4;
int n,K;
struct Edge {
int v,nxt;
}e[N<<1];
int head[N],etot;
int dep[N],siz[N];
int a[N];
bool mk[N];//true == tourism
int top[N];
inline int read() {
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
inline void adde(int u,int v) {
e[++etot].nxt=head[u],e[etot].v=v,head[u]=etot;
}
inline void dfs1(int p,int fa) {
dep[p]=dep[fa]+1;
siz[p]=1;
for (int i=head[p];~i;i=e[i].nxt) {
int v=e[i].v;
if (v^fa) {
dfs1(v,p);
siz[p]+=siz[v];
}
}
}
inline bool cmp(int i,int j) {
return siz[i]-dep[i]>siz[j]-dep[j];
}
inline void dfs2(int p,int fa,int tp) {
top[p]=tp;
for (int i=head[p];~i;i=e[i].nxt) {
int v=e[i].v;
if (v^fa) {
if (mk[p]&&!mk[v]) dfs2(v,p,v);
else dfs2(v,p,tp);
}
}
}
int main() {
memset(head,-1,sizeof(head));
memset(mk,false,sizeof(mk));
n=read(),K=read();
for (register int i=1;i<n;++i) {
int u=read(),v=read();
adde(u,v);
adde(v,u);
}
dfs1(1,0);
for (register int i=1;i<=n;++i) a[i]=i;
sort(a+1,a+n+1,cmp);
for (register int i=1;i<=n-K;++i) mk[a[i]]=true;
dfs2(1,0,-1);
ll ans=0;
for (register int i=n-K+1;i<=n;++i)
ans+=dep[top[a[i]]]-1;
cout<<ans<<endl;
return 0;
}