zoukankan      html  css  js  c++  java
  • 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)bchoose 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 1le ile k,0le x_ile 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^3log 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) 的情况。详见此处:https://www.cnblogs.com/yyf0309/p/ur19.html#4564642. 时间复杂度 (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
    ",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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    认识Cookie和状态管理
    python之requests库使用问题汇总
    如何掌握所有的程序语言--王垠
    非 GUI 模式运行 JMeter 压力测试
    Python 如何将字符串转为字典
    python之operator操作符函数
    python3使用xlrd、xlwt、xlutils、openpyxl、xlsxwriter操作excel
    《敏捷软件测试:测试人员与敏捷团队的实践指南》
    Jmeter跨线程组传递变量
    Jmeter获取当前时间、历史时间、未来时间的方式
  • 原文地址:https://www.cnblogs.com/suncongbo/p/12800633.html
Copyright © 2011-2022 走看看