zoukankan      html  css  js  c++  java
  • 题解-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}^nfrac {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)inom {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) 中挑一个消它
    • (inom {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
    ", f[n]);
    	return 0;
    }
    
  • 相关阅读:
    【SQLServer】 查询一个字段里不同值的最新一条记录
    【MySQL】mysql5.7数据库的安装和配置
    【Java】JDK安装及环境变量配置
    【Oracle】SQL语句优化
    【JavaScript】 控制自适应高度
    【Java】登录验证码
    【JavaScript】 直接下载保存文件
    【Java】Java批量文件打包下载zip
    【Java】Java 单文件下载及重命名
    主机在无线网络的情况下,设置centos7.2虚拟机网络联通
  • 原文地址:https://www.cnblogs.com/penth/p/10891179.html
Copyright © 2011-2022 走看看