Codeforces Round #635 C. Linova and Kingdom(贪心/树/好题)
Writing light novels is the most important thing in Linova's life. Last night, Linova dreamed about a fantastic kingdom. She began to write a light novel for the kingdom as soon as she woke up, and of course, she is the queen of it.
There are nn cities and n−1n−1 two-way roads connecting pairs of cities in the kingdom. From any city, you can reach any other city by walking through some roads. The cities are numbered from 11 to nn, and the city 11 is the capital of the kingdom. So, the kingdom has a tree structure.
As the queen, Linova plans to choose exactly kk cities developing industry, while the other cities will develop tourism. The capital also can be either industrial or tourism city.
A meeting is held in the capital once a year. To attend the meeting, each industry city sends an envoy. All envoys will follow the shortest path from the departure city to the capital (which is unique).
Traveling in tourism cities is pleasant. For each envoy, his happiness is equal to the number of tourism cities on his path.
In order to be a queen loved by people, Linova wants to choose kk cities which can maximize the sum of happinesses of all envoys. Can you calculate the maximum sum for her?
The first line contains two integers nn and kk (2≤n≤2⋅1052≤n≤2⋅105, 1≤k<n1≤k<n) — the number of cities and industry cities respectively.
Each of the next n−1n−1 lines contains two integers uu and vv (1≤u,v≤n1≤u,v≤n), denoting there is a road connecting city uu and city vv.
It is guaranteed that from any city, you can reach any other city by the roads.
Print the only line containing a single integer — the maximum possible sum of happinesses of all envoys.
7 4 1 2 1 3 1 4 3 5 3 6 4 7
7
4 1 1 2 1 3 2 4
2
8 5 7 5 1 7 6 1 3 7 8 3 2 1 4 5
9
裂了,脑子不好使对着假算法调了快两个小时...最后发现只需要改一下排序的条件就行...
引理(划掉):
当决定选某个节点x作为工业城市时,以x为根的子树的其余节点都应该被选为工业城市。
证明(划掉):
假设子树里还有不是工业城市的节点y,那么选y的话,对于最终答案减少得更少,增加的更多,因此是更优选择。(可以举几个例子理解下,这里是瞎bb的)
首先第一遍dfs预处理出每个节点的深度以及这个节点为子树根的子树的大小(常规操作)。
然后需要选出工业城市,也就是本题最关键的地方,按照合适的贪心策略对节点进行排序选出k个点,
只按照深度/子树大小显然是错的,先深度再子树大小或者深度/子树大小会被链+链菊花这种卡掉,都是没有正确的考虑每个节点对答案的最大贡献(比如我)。
正确的做法是应该这样考虑:当对于是否选x进行决策时,x的子节点必然被选过了,那么考虑选取x后对整个答案的影响:首先看答案能增加多少,显然是x前面的旅游城市个数。由引理可知,x前面的此时都是旅游城市,故增加的即x的深度减一。其次看答案减少多少,x为根的子树里的所有节点到树根(首都)的路径上都多了一个x,因此增加x为工业城市对答案的贡献为负的x的子树大小(不包括x)
更形式化地,贡献为(x.depth-1)-(x.size-1)=x.depth-x.size,所以把所有节点按照贡献从大到小排序,选取前k个点,这就是正确的贪心策略了。
最后再跑一边DFS更新答案即可。总的复杂度为O(nlogn)。
#include <bits/stdc++.h> #define N 200005 using namespace std; int n,k,head[N],ver[2*N],Next[2*N],tot=0; bool pd[N]={0};//pd[i]为1的话表示i是工业城市 long long ans=0; struct point { int num;//节点的序号 long long vis;//等价于以这个节点为根的子树的大小 int dist;//这个节点的深度 }p[N]; bool cmp(point a,point b) { // if(a.dist!=b.dist) // { // return a.dist>b.dist; // } // else return a.vis<b.vis; // return a.dist*b.vis>b.dist*a.vis; 这两种贪心策略会被一条长单链+一条长单链末尾连菊花给hack掉 return a.dist-a.vis>b.dist-b.vis; } void add(int x,int y) { ver[++tot]=y,Next[tot]=head[x],head[x]=tot; } long long dfs(int x,int d,int pre)//第一遍dfs进行预处理 { int i; p[x].dist=d;//获取深度 long long cnt=1; for(i=head[x];i;i=Next[i]) { int y=ver[i]; if(y==pre)continue; cnt+=dfs(y,d+1,x); } p[x].vis=cnt;//获取子树大小 return cnt; } void dfs1(int x,long long cnt,int pre)//第二遍dfs获取答案 cnt是从这个点到树根的路径上的旅游城市的数目 { long long temp=cnt; if(pd[x]==0)temp++;//如果当前点是旅游城市的话 相当于cnt+1 else { ans+=cnt;//不是的话代表这个城市是工业城市,需要把这个城市到根节点路径上的旅游城市的数目也就是cnt累加到答案里 } int i; for(i=head[x];i;i=Next[i]) { int y=ver[i]; if(y==pre)continue; dfs1(y,temp,x); } } int main() { cin>>n>>k; int i; memset(pd,0,sizeof(pd)); for(i=1;i<=n-1;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y);//建双向边 add(y,x); p[i].num=i,p[i].vis=0,p[i].dist=0;//初始化 } p[n].num=n,p[n].vis=0,p[n].dist=0; dfs(1,1,0); sort(p+1,p+n+1,cmp);//排序 for(i=1;i<=k;i++) { pd[p[i].num]=1;//标记工业城市 } dfs1(1,0,0); cout<<ans; return 0; }