UOJ #514 [UR #19]通用测评号 (容斥原理、DP)

题目链接

http://uoj.ac/contest/51/problem/514

题解

神仙们都好强啊。

本题有好多做法,但是第一步都是一样的:
题目中的“每次选一个没有达到 \(a\) 的进行装填”其实没有用,可以等价成每次随机选任何一个位置 \(+1\),然后求 \(\ge a\) 的个数的期望。
然后考虑计算 \(1\) 号位置最后达到 \(a\) 了的概率。

不容斥做法

考虑操作序列,对每个位置 \(+1\) 视为一种不同的操作。我们将一种非 \(1\) 操作的次数达到 \(b\) 或者 \(1\) 操作达到 \(a\) 称为“终止”。一种操作终止之后,我们忽略这种操作,不再加入进操作序列中。于是操作序列的总长是 \((n-1)b+a\).
考虑计算最后一次为 \(1\) 操作的概率。那么也就是说剩下 \((n-1)\) 种操作要在之前全部终止。设终止的时间从小到大排序为 \(t_1,t_2,...,t_{n-1}\),那么容易得到概率为

\[\frac{\prod^{n-1}_{i=1}{t_i-1-(i-1)b\choose b-1}}{\prod^{n-1}_{i=1}(n-i+1)^{t_i-t_{i-1}}} \]

直接对这个东西做一个 DP,设 \(f[i][j]\) 表示目前操作序列放了 \(i\) 个元素,已经有 \(j\) 种操作终止了。
时间复杂度 \(O(n^3)\).

容斥做法

容斥。考虑枚举 \(1\) 号位置达到 \(a\) 时没有达到 \(b\) 的位置个数 \(i\). 那么别的位置照样可以不考虑。
假设钦定了 \(k\) 个位置,连带着 \(1\) 实际被操作的次数分别记为 \(x_i\),其中 \(1\) 的次数记作 \(x_0\),则 \(\forall 1\le i\le k,0\le x_i\le b-1\). 考虑计算当 \(x_0=a-1\) 的时候,出现每个局面的概率之和,再乘以下一步操作 \(1\) 的概率 \(\frac{1}{k}\). 则对于一组合法的 \(x_0=a-1,x_1,...,x_k\),概率为

\[\frac{(\sum^k_{i=1}b_i)!}{\prod b_i!k^{\sum^k_{i=1}b_i}} \]

对所有钦定方案的所有 \(b\) 序列求和,直接 DP 可以做到 \(O(n^4)\),NTT 优化可以做到 \(O(n^3\log n)\).

有一个神奇的优化:发现我们就是要求一个生成函数 \(F(x)\sum^{b-1}_{i=0}\frac{x^i}{i!}\)\(0,1,...,n\) 次幂的每一项系数。而这种长得很像 \(e^x\) 的函数,求导往往有奇效:

\[(F(x)^i)'=iF(x)^{i-1}F'(x)=iF(x)^i-iF(x)^{i-1}\cdot \frac{x^{b-1}}{(b-1)!} \]

这个式子可以一项一项地推出来系数,时间复杂度 \(O(n^3)\).

然后还有一种神奇的 DP,大概是从前往后考虑操作序列,减掉当前这个数出现次数 \(\ge b\) 的情况。时间复杂度 \(O(n^3)\).

代码

不容斥做法

#include<bits/stdc++.h>
#define llong long long
#define mkpr make_pair
#define x first
#define y second
#define iter iterator
#define riter reversed_iterator
#define y1 Lorem_ipsum_dolor
using namespace std;

inline int read()
{
	int x = 0,f = 1; char ch = getchar();
	for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
	for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
	return x*f;
}

const int mxN = 250;
const int P = 998244353;

void updsum(llong &x,llong y) {x+=y-P,x+=(x>>31)&P;}

llong quickpow(llong x,llong y)
{
	if(y==0) {return 1ll;}
	llong ret = quickpow(x,y>>1); ret = ret*ret%P; if(y&1ll) {ret = ret*x%P;}
	return ret;
}
llong fact[mxN*mxN+3],facti[mxN*mxN+3],inv[mxN*mxN+3];
void initfact(int n)
{
	fact[0] = 1ll; for(int i=1; i<=n; i++) fact[i] = fact[i-1]*i%P;
	facti[n] = quickpow(fact[n],P-2); for(int i=n-1; i>=0; i--) facti[i] = facti[i+1]*(i+1ll)%P;
	for(int i=1; i<=n; i++) inv[i] = facti[i]*fact[i-1]%P;
}
llong comb(llong x,llong y) {return x<0||y<0||x<y?0ll:fact[x]*facti[y]%P*facti[x-y]%P;}

llong f[mxN*mxN+3][mxN+3];
int n,a,b; llong ans;

int main()
{
	initfact(mxN*mxN);
	n = read(),a = read(),b = read();
	f[0][0] = 1ll;
	for(int i=0; i<=(n-1)*b+a-1; i++)
	{
		for(int j=0; j<=n-1&&j*b<=i; j++)
		{
			llong x = f[i][j]; if(!x) continue;
//			printf("f[%d][%d]=%lld\n",i,j,x);
			x = x*inv[n-j]%P;
			updsum(f[i+1][j],x);
			updsum(f[i+1][j+1],x*comb(i-j*b,b-1)%P);
		}
	}
	ans = f[(n-1)*b+a-1][n-1];
	ans = (1ll-ans*fact[n-1]%P+P)*n%P;
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值