1. 程式人生 > 實用技巧 >CF1336A 【Linova and Kingdom】

CF1336A 【Linova and Kingdom】

題目翻譯:

有一棵 \(n\) 個節點的樹 ( $n \le 2 \times 10^5 $ ),現在要求選出 \(k\) 個節點,使得這 \(k\) 個節點到根節點的最短路徑中,每個節點經過的剩餘 \(n-k\) 個節點的數量之和最大。

思路:

注:

  • 這裡所說的 \(u\) 的子樹不包含 \(u\) 本身。
  • \(dep\) 指深度,\(siz\) 指子樹大小(包含子樹的根)。

先 dfs 一遍,把樹的深度和子樹大小處理出來,然後貪心。
如果直接按照深度或者子樹大小排序都是錯的,這是因為新選一個節點可能會導致答案減小。所以我們需要一種更優的排序策略。
因為選 \(u\) 答案增加 \(dep_u-1\)

,而選 \(u\) 子樹中的節點增加的值一定大於 \(dep_u-1\)。而且選 \(u\) 子樹中的節點,減少的值一定小於等於選 \(u\) 節點。所以如果要選 \(u\) 節點,那麼 \(u\) 結點的子樹一定都選完了。
所以我們排序的時候就認為當選 \(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);
	}
}