[Contest on 2020.11.21] 树上竞技

( ext{Description})

传送门

( ext{Solution})

他们通过无线通讯设备确定一个集合点,然后所有人沿最短路径走到集合点,花费的代价为所有人走的路径长度之和。 为了取得竞技的胜利,他们需要最小化花费的代价。

其实如果是指定节点/任意节点最小化花费应该都是很好做的换根 ( ext{DP})

然而他们并不知道他们最终会进入哪个节点,所以你需要对于每种不同的情况计算最小代价并求出总和。

这个就很艹了,如果用普通的 (f[i][j]) 定义(枚举集合点)并不好做:算总方案数就没法保证最小化,算 (min) 就没法算总方案。

头大。

正解是对于每条线段来算贡献。考虑一条边两端关键点个数分别为 (x,m-x),如果有 (xle m-x),肯定会把集合点设在 (m-x) 的那个连通块里,这样它只会被经过 (x) 次。

假设一条边两侧分别有 (s,n-s) 个点,那么这条边的贡献就是:

[sum_{x=1}^{m-1} ext C(s,x) imes ext C(n-s,m-x) imes min(x,m-x) ]

这样肯定过不去,考虑优化掉 (min),其实如果强制 (xle m-x) 就有 (xle lfloorfrac{m}{2} floor)。考虑 (x) 范围只有 ([1,m-1]),把偶数的 (frac{m}{2}) 情况刨开,就有,

[sum_{x=1}^{lfloorfrac{m-1}{2} floor} ext C(s,x) imes ext C(n-s,m-x) imes x +sum_{x=1}^{lfloorfrac{m-1}{2} floor} ext C(n-s,x) imes ext C(s,m-x) imes x+[m\%2==0] ext C(s,frac{m}{2}) imes ext C(n-s,frac{m}{2}) imes frac{m}{2} ]

那么我们只用快速求(前面两项差不多):

[sum_{x=1}^{lfloorfrac{m-1}{2} floor} ext C(s,x) imes ext C(n-s,m-x) imes x ]

到这里就有两种思路:将求和去掉或预处理后 (mathcal O(1)) 递推。

可是我数学差只会看题解,题解写什么就是什么 (qwq)

(lfloorfrac{m-1}{2} floor=k),就有:

[s imes sum_{x=1}^{k} ext C(s-1,x-1) imes ext C(n-s,m-x) ]

并令其为 (s imes g(s))

这个柿子有组合意义(就是排列组合的经典模型):在 (n-1) 个球里选 (m-1) 个球,保证前 (s-1) 中最多选 (k-1) 的方案数。显然 (g(s+1)) 相对于 (g(s)) 只少了在前 (s-1) 选了 (k-1) 个球,又选了第 (s) 个球的情况(考虑限制更紧,肯定会变少),这种方案数可以表示为 ( ext C(s-1,k-1) imes ext C(n-1-s,m-1-k)):在前 (s-1) 选了 (k-1) 个球,(n-1-s) 表示除了前 (s) 个球的球数,(m-1-k) 表示前面 (s) 个球选了 (k) 个,那么后面 (n-1-s) 个球要选 (m-1-k) 个,显然这样强制了选了第 (s) 个球。

( ext{Code})

#include <cstdio>

#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(register signed i=(_l),_end=(_r);i>=_end;--i)
#define erep(i,u) for(signed i=head[u],v=to[i];i;i=nxt[i],v=to[i])
#define efep(i,u) for(signed i=Head[u],v=to[i];i;i=nxt[i],v=to[i])
#define print(x,y) write(x),putchar(y)

template <class T> inline T read(const T sample) {
    T x=0; int f=1; char s;
    while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
    while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
    return x*f;
}
template <class T> inline void write(const T x) {
    if(x<0) return (void) (putchar('-'),write(-x));
    if(x>9) write(x/10);
    putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
template <class T> inline T fab(const T x) {return x>0?x:-x;}
template <class T> inline T gcd(const T x,const T y) {return y?gcd(y,x%y):x;}
template <class T> inline T lcm(const T x,const T y) {return x/gcd(x,y)*y;}
template <class T> inline T Swap(T &x,T &y) {x^=y^=x^=y;}

const int mod=1e9+7,maxn=1e6+5; 

int n,m,fac[maxn],ifac[maxn],k,siz[maxn],ans,g[maxn];
int head[maxn],to[maxn<<1],nxt[maxn<<1],cnt;

void addEdge(int u,int v) {
	nxt[++cnt]=head[u],to[cnt]=v,head[u]=cnt;
	nxt[++cnt]=head[v],to[cnt]=u,head[v]=cnt;
}

int qkpow(int x,int y) {
	int r=1;
	while(y) {
		if(y&1) r=1ll*r*x%mod;
		x=1ll*x*x%mod; y>>=1;
	}
	return r;
}

void init() {
	fac[0]=1;
	rep(i,1,n) fac[i]=1ll*fac[i-1]*i%mod;
	ifac[n]=qkpow(fac[n],mod-2);
	fep(i,n-1,0) ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
}

int C(int x,int y) {
	if(x<y) return 0;
	return 1ll*fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}

void dfs(int u,int fa) {
	siz[u]=1;
	erep(i,u) {
		if(v==fa) continue;
		dfs(v,u);
		siz[u]+=siz[v];
		ans=((ans+g[siz[v]])%mod+g[n-siz[v]])%mod;
		if(!(m&1)) ans=(ans+1ll*C(siz[v],m>>1)*C(n-siz[v],m>>1)%mod*(m>>1)%mod)%mod;
	} 
}

int main() {
//	freopen("meeting.in", "r", stdin);
//	freopen("meeting.out", "w", stdout);
	int x;
	n=read(9),m=read(9); k=m-1>>1; init();
	rep(i,2,n) x=read(9),addEdge(x,i);
	if(k>=1) g[1]=C(n-1,m-1); // 注意特判这个时候没有数值
	rep(i,1,n-1) g[i+1]=(g[i]-1ll*C(i-1,k-1)*C(n-1-i,m-1-k)%mod+mod)%mod;
	rep(i,1,n) g[i]=1ll*g[i]*i%mod;
	dfs(1,0);
	print(ans,'
');
	return 0;
} 
原文地址:https://www.cnblogs.com/AWhiteWall/p/14055854.html