题解-CSA Beta Round#1 Number Elimination

Problem

CSA-Beta Round#3

题意概要:给定 \(n\) 个数组成的序列,定义一次操作:

  • 在当前序列中选择两个数,将其中较小的数从序列中删除(若两个数相同,则删除在序列中更靠前的)
  • 较大的数为此次操作的花费

进行 \(n-1\) 次操作后只剩一个数,求有多少种不同的执行方案,满足其花费为所有方案中花费最少的

Solution

发现网上没有什么CSA方面的资料?

首先有一个贪心,就是将数字排序离散后压缩一下,若数字 \(i\)\(s_i\) 个,则一定先来 \(s_i-1\) 次花费为零的操作(每次选择两个 \(i\)),剩下的一个 \(i\) 一定是被一个 \(i+1\) 删除

然后考虑用操作的拓扑图来统计……没辙,正解是序列 \(dp\)

先设 \(dp[i]\) 表示 \(i\) 个相同的数字互相消除直至只剩一个数字的方案数,易得 \(dp[n] = \prod_{i=2}^n\frac {i(i-1)}2\)

再考虑设 \(f[i]\) 为前 \(i\) 堆数字已经消成只剩一个的方案数

考虑在处理完前 \(i-1\) 堆的基础上加入第 \(i\) 堆,不妨枚举在第 \(i-1\) 堆的最后一个元素被消除之前有多少个 \(i\) 被消除了,易得:

\[f[i]=\sum_{x=0}^{s_i-1}f[i-1]\cdot dp[s_i](s_i-x)\binom {\sum_{j=1}^{i-1}s_i-1+x}x \]

一个个解释:

  • \(f[i-1]\):前 \(i-1\) 堆消成一个 \(i-1\) 的方案数
  • \(dp[s_i]\):这 \(s_i\)\(i\) 消成一个的方案数
  • \(s_i-x\):前 \(i-1\) 堆消剩下的那一个 \(i-1\) 需要从剩下的 \(s_i-x\)\(i\) 中挑一个消它
  • \(\binom {\sum_{j=1}^{i-1}s_i-1+x}x\):考虑将这 \(x\) 个提前消的融入前面的消除序列(这里不使用排列是因为这 \(x\)\(i\) 内部之间的前后关系已经在 \(dp[s_i]\) 中考虑过了)

总的来说,就是将 \(s_i\) 分为两段,一份放入前面一起大排队,另一份用来消剩下的那个 \(i-1\)

复杂度 \(O(\sum s_i)=O(n)\)

Code

#include <bits/stdc++.h>
typedef long long ll;

inline void read(int&x){
	char ch=getchar();x=0;while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
}

const int N = 101000, p = 1e9+7;
int a[N], sum[N], f[N], dp[N], n;

inline int qpow(int A, int B) {
	int res = 1; while(B) {
		if(B&1) res = (ll)res * A%p;
		A = (ll)A * A%p, B >>= 1;
	} return res;
}

int fac[N], inv[N];
inline int C(const int nn, const int mm) {return (ll)fac[nn] * inv[mm]%p * inv[nn-mm]%p;}

int main() {
	read(n);
	for(int i=1;i<=n;++i) read(a[i]);
	std::sort(a+1,a+n+1);
	
	dp[0] = dp[1] = fac[0] = fac[1] = 1;
	for(int i=2;i<=n;++i) {
		fac[i] = (ll)fac[i-1] * i%p;
		dp[i] = ((ll)i * (i-1) >> 1) * dp[i-1]%p;
	}
	inv[n] = qpow(fac[n], p-2);
	for(int i=n;i;--i) inv[i-1] = (ll)inv[i] * i%p;
	
	int t = 0;
	for(int i=1,j=1;i<=n;i=j) {
		while(a[i] == a[j] and j <= n) ++j;
		a[++t] = j - i, sum[t] = (a[t] + sum[t-1])%p;
	}
	n = t;
	
	f[1] = dp[a[1]];
	for(int i=2;i<=n;++i) {
		for(int x = 0; x < a[i]; ++ x)
			f[i] = (f[i] + (ll)C(sum[i-1]-1+x,x) * (a[i]-x))%p;
		f[i] = (ll)f[i] * f[i-1]%p * dp[a[i]]%p;
	}
	printf("%d\n", f[n]);
	return 0;
}
posted @ 2019-05-19 22:23  oier_hzy  阅读(187)  评论(0编辑  收藏  举报