CF1336A 【Linova and Kingdom】
阿新 • • 發佈:2020-08-23
題目翻譯:
有一棵 \(n\) 個節點的樹 ( $n \le 2 \times 10^5 $ ),現在要求選出 \(k\) 個節點,使得這 \(k\) 個節點到根節點的最短路徑中,每個節點經過的剩餘 \(n-k\) 個節點的數量之和最大。
思路:
注:
- 這裡所說的 \(u\) 的子樹不包含 \(u\) 本身。
- \(dep\) 指深度,\(siz\) 指子樹大小(包含子樹的根)。
先 dfs 一遍,把樹的深度和子樹大小處理出來,然後貪心。
如果直接按照深度或者子樹大小排序都是錯的,這是因為新選一個節點可能會導致答案減小。所以我們需要一種更優的排序策略。
因為選 \(u\) 答案增加 \(dep_u-1\)
所以我們排序的時候就認為當選 \(u\) 時,它的子樹都已經被選完。
所以選節點 \(u\) 會增加的值就是:
- \((dep_u-1) \times siz_u\)
選節點 \(u\) 會減少的值就是:
- \((siz_u-1)\times dep_u\)
選節點 \(u\) 對整個答案的貢獻就是:
- \((dep_u-1)\times siz_u-(siz_u-1) \times dep_u\)
按照這個貢獻排序,選前 \(k\) 名即可。
答案再 dfs 一遍就可以得到。
Code:
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<bitset> #include<cmath> #include<queue> #include<map> #include<set> #define LL long long using namespace std; LL read() { LL ans=0,f=1; char c=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return ans*f; } const LL N=1e6+5; LL n,k,is[N],sum[N],ans; struct Edge { LL v,ne; }e[N]; LL head[N],tot; struct Node { LL dep,x,siz; // 分別代表 深度 ,編號 ,子樹大小 bool operator < (const Node &x)const { return ((x.dep-1)*x.siz-x.dep*(x.siz-1))<((dep-1)*siz-dep*(siz-1)); // 這是上面推出的排序策略 } }node[N]; inline void add(LL u,LL v); void dfs1(LL u,LL fa); void dfs2(LL u,LL fa); int main() { n=read();k=read(); for(LL i=1;i<=n-1;++i) { LL u=read(),v=read(); add(u,v); add(v,u); // 注意無向邊 } dfs1(1,0); sort(node+1,node+1+n); for(LL i=1;i<=k;++i) // 選出前 k 個節點 is[node[i].x]=1; dfs2(1,0); printf("%lld",ans); return 0; } inline void add(LL u,LL v) { e[++tot]=(Edge){v,head[u]}; head[u]=tot; } void dfs1(LL u,LL fa) // 第一遍 dfs 預處理出每個節點的深度與子樹大小 { node[u].dep=node[fa].dep+1; node[u].siz=1; node[u].x=u; for(LL i=head[u];i;i=e[i].ne) { LL v=e[i].v; if(v==fa) continue; dfs1(v,u); node[u].siz+=node[v].siz; } } void dfs2(LL u,LL fa) // 第二遍 dfs 利用字首和求答案 { if(!is[u]) sum[u]++; else ans+=sum[u]; for(LL i=head[u];i;i=e[i].ne) { LL v=e[i].v; if(v==fa) continue; sum[v]=sum[u]; dfs2(v,u); } }