zoukankan      html  css  js  c++  java
  • 【集训队作业2018】喂鸽子

    我的计数还是太差了……

    这道题现在知道三种做法。

    1. 直接DP

    首先显然需要min-max容斥(不知道请百度),不然很难算。

    显然对于大小相同的集合答案一样,问题转化为求 (f_c)(c) 只鸽子最早有一只搞满 (k) 个玉米的期望。

    然后我就有了一种 (f_{c,k}) 表示 (c) 只,搞满 (k) 颗,直接DP的想法,不知道能不能做。

    这道题考虑期望即枚举步数乘上概率 (sum_s s imes p(s)) 比较难搞,但是因为是 (s) 次,所以不如考虑一个经典的trick,考虑小于 (s) 的,全部贡献。

    那么原式 (sum_s s imes p(s) = sum_s p'(s)),其中 (p'(s)) 为大于 (s) 步的概率。

    考虑 (p'(s)),大于等于 (s) 步即 (s) 步以内里面没有一个饱,枚举里面吃了几个。那么记 (g_{c,s}) 为铥了 (s) 个,有 (c) 只,没有一个饱的概率。

    [p'(s) = sum_i inom{s}{i} g_{c,i} left( frac{n - c}{n} ight)^{s - i} ]

    但是带入计算答案的式子,计算一个 (c) 复杂度是 (O(n^2k^2)) 的,甚至还要算 (n)(c)

    所以先考虑优化这个式子。第一个想法肯定是卷积。

    带上计算答案的式子,对于一个 (c),有

    [f_c = sum_{s = 0} g'(s) = sum_{s = 0} sum_i inom{s}{i} g_{c,i} left( frac{n - c}{n} ight)^{s - i} ]

    显然可以换元,枚举 ((s - i) + (i) = s),得到

    [f_c = sum_i g_{c,i} sum_j inom{i + j}{i} left( frac{n - c}{n} ight)^{j} ]

    显然后者可以通过一些方法算到 (O(log n)) 或者更低。即 (x = frac{n - c}{n}),然后就变成了 (left( frac{1}{1-x} ight)^C) 这一类的式子。

    那么只要能快速计算 (g_{c,s}) 就能快速得到 (f)

    显然 (g) 可以通过不断加入一个点来转移,枚举多少步,就是一个组合数卷积(其实EGF可以推的啊)。

    [g_{c,s} = sum_i inom{s}{i} g_{c - 1, s - i} left( frac{1}{n} ight)^{i} ]

    显然表示成 EGF 可以暴力卷积。

    复杂度 (O(n^2 k (log n + log k)))

    目前懒得写了。

    2. 更好的做法

    一个截然不同的做法,直接对题目做。如果把一个鸽子得到的前 (k) 个都当做有效的,那么有效的只有 (nk) 个。

    所以枚举每次有效插入之前多少个饱了的,记这个序列为 (A),那么,每要得到一个有效的,期望步数就是加上 (frac{n}{rest})

    但是得到了这个玉米我们怎么知道哪个鸽子会不会饱啊

    所以假设玉米扔到了一个等待序列中,等着被安排,直到我们要迅速撑饱一个鸽子的时候组合数取一下玉米,再在剩下的鸽子里钦定一个。注意最后一个玉米是已经确定好的,所以只要选 (K - 1) 个。

    如果这样做会出问题,在我们搞那个等待序列的时候,就已经多算贡献了。注意到我们的期望步数是选可选集合中的一个,因此那个玉米自带了从属的鸽子,自然会多算。所以要乘上一个 (frac{1}{rest}) 抵消掉这个贡献,以实现加入等待序列。

    于是考虑转移,显然状态里记录一下已经放的玉米和已经饱的鸽子个数就可以实现 (O(1)) 转移,只要同时记录方案数和期望即可。

    复杂度 (O(n^2k))

    在奇怪的地方上调了好久。拿阶乘逆来当普通的逆了……

    #include <bits/stdc++.h>
    
    const int mod = 998244353;
    typedef long long LL;
    int mul(int a, int b) { return (LL) a * b % mod; }
    void reduce(int & x) { x += x >> 31 & mod; }
    const int MAXN = 50010;
    int fac[MAXN], inv[MAXN];
    int C(int a, int b) { return a < b ? 0 : (LL) fac[a] * inv[b] % mod * inv[a - b] % mod; }
    
    int f[51][MAXN], g[51][MAXN];
    int n, K;
    int main() {
    	fac[0] = fac[1] = inv[0] = inv[1] = 1;
    	for (int i = 2; i != MAXN; ++i) {
    		fac[i] = mul(fac[i - 1], i);
    		inv[i] = mul(inv[mod % i], mod - mod / i);
    	}
    	for (int i = 2; i != MAXN; ++i)
    		inv[i] = mul(inv[i - 1], inv[i]);
    	std::cin >> n >> K; const int T = n * K;
    	f[0][0] = 1;
    	for (int j = 0; j < T; ++j)
    		for (int i = 0; i < n; ++i) if (f[i][j]) {
    			int c1 = mul(inv[n - i], fac[n - i - 1]), c2 = mul(c1, n);
    			reduce(f[i][j + 1] += mul(f[i][j], c1) - mod);
    			reduce(g[i][j + 1] += ((LL) f[i][j] * c2 % mod + g[i][j]) * c1 % mod - mod);
    			c1 = mul(c1, C(j - i * K, K - 1));
    			reduce(f[i + 1][j + 1] += mul(f[i][j], c1) - mod);
    			reduce(g[i + 1][j + 1] += ((LL) f[i][j] * c2 % mod + g[i][j]) * c1 % mod - mod);
    		}
    	std::cout << mul(g[n][T], fac[n]) << std::endl;
    	return 0;
    }
    

    3. 最通用的做法

    还是min-max容斥。但是求下面的期望我们可以直接生成函数。

    我们记录下放进钦定的 (c) 个鸽子,记有效的玉米为丢个这几个钦定的鸽子的玉米。有效玉米构成了一个序列。显然它们取到 (s) 个玉米的期望步数为 (frac{sn}{c})

    对一个长度,实际上只需要求出有贡献的方案数,以及总方案数。总方案数就是 (frac{1}{c^s}),即这个序列所有可能。

    对于有贡献的,可以采用指数型生成函数。我们钦定第一个吃饱的是 (1),然后最后方案数只要乘上一个 (c)

    因为最后一颗是确定的,所以只需要知道第一只吃了 (k - 1) 个,其他的小于等于 (k - 1) 个的方案数。因为是排列,所以使用 EGF

    [frac{x^{k - 1}}{(k - 1)!} left(1 + x + frac{x^2}{2!} + dots + frac{x^{k - 1}}{(k - 1)!} ight)^{c - 1} ]

    不阻止你们使用Poly Ln EXP,复杂度 (O(n^2 k (log n + log k))) 。但是为了优美,所以考虑使用推式子技巧。

    左边那项乘上特别简单,那么只考虑右边系数的计算。看样子不太好做,所以直接上万能的求导积分等操作,可以方便的求出递推式。

    [egin{align*} & frac{mathrm{d} left(1 + x + frac{x^2}{2!} + dots + frac{x^{k}}{k!} ight)^c}{mathrm{dx}} \ = & c imes left(1 + x + frac{x^2}{2!} + dots + frac{x^{k}}{k!} ight)^{c - 1} imes left(1 + x + frac{x^2}{2!} + dots + frac{x^{k - 1}}{(k - 1)!} ight) end{align*} ]

    然后积分一下。但是里面求的是一个卷积,那样的话还是得用FFT。

    当然可以优化了。定义

    [f(x) = left(1 + x + frac{x^2}{2!} + dots + frac{x^{k}}{k!} ight) ]

    注意到 (f'(x) = f(x) - frac{x^{k}}{k!}),则

    [egin{align*} & frac{mathrm{d} f^c(x)}{mathrm{dx}} \ = & c f^{c-1}(x) imes (f(x) - frac{x^{k}}{k!}) \ = & c f^c(x) - frac{x^{k}}{k!} f^{c-1}(x) end{align*} ]

    对应一下系数,因为积分了,所以 (x^{n - 1}) 贡献到 (x^{n}),于是有了一个递推式,显然可以 (O(1)) 求出一项。

    再结合上面讲的,就 (O(n^2 k)) 过了此题。

    这个做法是从 ( exttt{@yhx-12243}) 那里学的,/cy

  • 相关阅读:
    pro-engineer&UG
    鸡肋
    中国计算机软件设计师考试大纲 2008-10-12 21:51 鹤影314 | 五级
    成为JAVA软件开发工程师要学哪些东西
    爱上一匹野马,可我的家里没有草原,这让我感到绝望。
    理解MySQL——架构与概念
    理解MySQL——索引与优化
    负载均衡与高可用概念(转)
    Nginx Rewrite详解(转)
    Nginx 反向代理配置实例(转)
  • 原文地址:https://www.cnblogs.com/daklqw/p/11592740.html
Copyright © 2011-2022 走看看