碰头

220 篇文章 2 订阅
98 篇文章 0 订阅

题目

题目背景
景区的石桥,下面有通道,于是有标牌 “小心碰头” 。

下面还有一行英语,写着 c a r e f u l l y      m e e t \tt carefully\;\;meet carefullymeet

题目描述
沐目女未 有 n n n 个房子,有 n − 1 n-1 n1 条小路连接着它们,并保证两个房子之间互相可达。

现在有 m m m 个人去找 沐目女未。但是他们具体在哪里我们并不知道,沐目女未 的所在,我们也不知道。

于是这 m m m 个人准备确定一个集合点,使得每个人延最短路走到集合点的总距离之和最小。这个集合点只能是某个房子。每条小路都是 1 km 1\text{km} 1km不要在乎有的人可能要走五万公里。

对于每一个可能的 m m m 个人的所在地,你都要计算出这个总距离,并输出它们的和。提示:一共有 ( n m ) {n\choose m} (mn) 个方案。

数据范围与提示
1 ≤ m ≤ n ≤ 1 0 6 1\le m\le n\le 10^6 1mn106

思路壹

把人存在的地方叫做特殊点。

首先你要看出,集合点是加权重心。说白了,以它为根,所有子树的特殊点数量不超过 ⌊ m 2 ⌋ \lfloor\frac{m}{2}\rfloor 2m

接下来,我们可以知道 每条边 被多少次经过,因为一定是特殊点较少的一边走到特殊点较多的一边。假设这条边断开后,形成的两个连通块的大小为 x x x n − x n-x nx ,那么贡献为

∑ i = 1 m − 1 ( x i ) ( n − x m − i ) × min ⁡ ( i , m − i ) \sum_{i=1}^{m-1}{x\choose i}{n-x\choose m-i}\times\min(i,m-i) i=1m1(ix)(minx)×min(i,mi)

由于 min ⁡ \min min 不好操作,把它分成两部分考虑,以 m m m 为奇数且 i ≤ m − 1 2 i\le\frac{m-1}{2} i2m1 为例。因为两边的形式是必然相同的——从现实意义上,我们只是讨论了 x x x n − x n-x nx 谁的特殊点更多。

这里我们要计算 ∑ i = 1 m − 1 2 ( x i ) ( n − x m − i ) × i \sum_{i=1}^{\frac{m-1}{2}}{x\choose i}{n-x\choose m-i}\times i i=12m1(ix)(minx)×i

按照惯例,组合数得用递推搞定。然后我就在这里死掉了。

如果你愿意再推一步,就会把式子变形成

x × ∑ i = 1 m − 1 2 ( x − 1 i − 1 ) ( n − x m − i ) x\times\sum_{i=1}^{\frac{m-1}{2}}{x-1\choose i-1}{n-x\choose m-i} x×i=12m1(i1x1)(minx)

(其实你直接得到这个式子也是很有道理的:在 x x x 里面选一个来计算贡献,然后剩下的乱放。)

我做到这一步了,然后放弃了。然后回头去想化简前的式子的递推,然后死球了。

接下来,右边的组合数可以递推。它的组合意义是 ( n − 1 m − 1 ) {n-1\choose m-1} (m1n1) 的这些方案中,前 x − 1 x-1 x1 个位置最多拿 m − 1 2 − 1 \frac{m-1}{2}-1 2m11 个。然后你发现可以直接用 f ( x − 1 ) f(x-1) f(x1) 递推到 f ( x ) f(x) f(x) ,只需要减去极少的不合法情况。

复杂度 O ( n ) \mathcal O(n) O(n) ,代码也非常简单。

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		(c == '-' ? f = -f : 0);
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int Mod = 1e9+7;
const int MaxN = 1000005;
const int_ inv2 = (Mod+1)>>1;

struct Edge{
	int to, nxt;
};
Edge e[MaxN];
int head[MaxN], cntEdge;
void addEdge(int a,int b){
	e[cntEdge].to = b;
	e[cntEdge].nxt = head[a];
	head[a] = cntEdge ++;
}

int siz[MaxN];
int dfs(int x){
	siz[x] = 1;
	for(int i=head[x]; ~i; i=e[i].nxt)
		siz[x] += dfs(e[i].to);
	return siz[x];
}

int_ jc[MaxN], inv[MaxN];
void prepare(int n){
	jc[1] = inv[1] = 1;
	for(int i=2; i<=n; ++i){
		inv[i] = (Mod-Mod/i)*inv[Mod%i]%Mod;
		jc[i] = i*jc[i-1]%Mod;
	}
	for(int i=2; i<=n; ++i)
		inv[i] = inv[i-1]*inv[i]%Mod;
	inv[0] = jc[0] = 1;
}
int_ C(int n,int m){
	if(m < 0 || n < m) return 0;
	return jc[n]*inv[m]%Mod*inv[n-m]%Mod;
}

int f[MaxN]; // f[x]: 前 x 拿最多 xez 个
int main(){
	// freopen("meeting.in","r",stdin);
	// freopen("meeting.out","w",stdout);
	int n = readint(), m = readint();
	for(int i=1; i<=n; ++i)
		head[i] = -1;
	for(int i=2; i<=n; ++i)
		addEdge(readint(),i);
	dfs(1), prepare(n);
	int xez = (m-1)>>1; // 最多选 xez-1 个
	f[0] = C(n-1,m-1); // 随便选
	for(int i=1; i<n; ++i){
		f[i] = f[i-1]-C(i-1,xez-1)
			*C(n-1-i,m-1-xez)%Mod;
		f[i] = (f[i]+Mod)%Mod; // 减去恰好超了
// printf("f[%d] = %d\n",i,f[i]);
	}
	if(m <= 2) // 特殊情况:什么也不选
		for(int i=0; i<n; ++i)
			f[i] = 0;
	int ans = 0;
	for(int i=2; i<=n; ++i){
		int_ x = siz[i];
		ans += (x*f[x-1]+(n-x)*f[n-x-1])%Mod;
		ans %= Mod; // 我这都能忘……我佛了
		if((m&1) == 0) // 两边相等,少算了一次
			ans= (ans+C(x,m>>1)*C(n-x,m>>1)
				%Mod*(m>>1))%Mod; // 会算两次
// printf("ans = %d\n",ans);
	}
	printf("%d\n",ans);
	return 0;
}

思路贰

知名博主 沐目女未 给了我一个想法。她枚举一个集合点,将其作为根的话,某个子树内的所有节点的出现次数都是相同的。怎么算呢?利用 ⌈ m + 1 2 ⌉ \lceil\frac{m+1}{2}\rceil 2m+1 最多出现一次,减去不合法即可。(这个技巧在去年 C S P \tt CSP CSP有出现过。)

然后我推了很久,发现很难继续。感兴趣的可以自己推一推式子。这里给一个提示,

( s i z M ) ( n − M m − M ) {siz\choose M}{n-M\choose m-M} (Msiz)(mMnM)

不能 计算出大小为 s i z siz siz 的子树中至少有 M M M 个特殊点的方案数的。因为不能分布进行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值