【SDOI 2017】龙与地下城(组合)

几率论太难了,不会。但这不能阻止咱们过题。
相信你们都会一个基于背包的暴力作法,咱们能够将其当作是卷积的形式就能够用fft优化了。形式化讲,就是求幂级数$ (\sum\limits_{i = 0}^{x - 1} \frac{1}{x} z^i)^y $在$[z^A, z^B]$之间的系数和。

不在模意义下的作法
直接将上述幂级数暴力倍增卷积求出来复杂度是$O(x*y*log(xy))$,不太能过的。但若是不在模意义下作咱们就能够尝试爆精度爆过去。很容易发现最后求得的点数和很大几率就是在均值附近的,过大太小的几率几乎为0。也就是咱们中间在作卷积和有不少项几乎为0,咱们把他们忽略掉是在精度的承受范围以内的。因而咱们在作倍增求出上述幂级数的时候咱们能够每次只保留中间的一小段有值的部分,其他的扔掉就好了。也就是说咱们设定一个阈值$\epsilon$,每次卷积后把多项式中值$\le \epsilon$的都扔掉。这样复杂度就是$O(len*log(len + y))$,其中$len$是最终幂级数中值$> \epsilon$的个数。实践证实,当$\epsilon$取$10^{-9}$时,$len$大概是两三万,而且此时的精度能够达到小数点后4位。

在模意义下的作法
若是答案能够对某个质数取模,这题就更好作了。
考虑最开始的那个幂级数:

$(\sum\limits_{i = 0}^{x - 1} \frac{1}{x} z^i)^y = (\frac{1}{x} \frac{1 - z^x}{1 - z})^y = (\frac{1}{x})^y (1 - z^x)^y(\frac{1}{1 - z})^y$

咱们想要求其在第$[A, B]$之间的系数和,咱们能够乘上一个$\frac{1}{1 - x}$来作一次前缀和,这样咱们能够转化成两次形如求一个$z^n$的系数的问题。也就是:

$[z^n] (\frac{1}{x})^y (1 - z^x)^y(\frac{1}{1 - z})^{y + 1}$

手动展开后面的两个:c++

$=[z^n] (\frac{1}{x})^y(\sum\limits_{i = 0}^{y}\binom{y}{i}(-1)^iz^{ix})(\sum\limits_{i = 0}\binom{y + i}{y}z^i)$

$=(\frac{1}{x})^y\sum\limits_{i = 0}^{y}\binom{y}{i}(-1)^i\binom{y + n - ix}{y}$ide

预处理组和数以后随便作一下就行了,复杂度$O(x*y)$。优化

 

$\bigodot$套路与技巧:spa

  • 隔板法用于展开形如$(\frac{1}{1 - z})^y$的幂级数。

 

作法一:code

#include <bits/stdc++.h>

using namespace std; namespace PO { const int N = 4e5 + 5; const double PI = acos(-1); struct Com { double x, y; friend Com operator + (Com a, Com b) { return (Com){ a.x + b.x, a.y + b.y }; } friend Com operator - (Com a, Com b) { return (Com){ a.x - b.x, a.y - b.y }; } friend Com operator * (Com a, Com b) { return (Com){ a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x }; } } ta[N], tb[N], w[N]; int rev[N], L = 1; typedef vector<Com> Poly; void Init(int l) { for (; L < l; L <<= 1) { for (int i = L; i < (L << 1); ++i) { w[i] = (Com){ cos(PI / L * (i - L)), sin(PI / L * (i - L)) }; } } for (int i = 0; i < l; ++i) { rev[i] = (rev[i >> 1] >> 1) | (i & 1? l >> 1 : 0); } } void Dft(Com *a, int l) { for (int i = 0; i < l; ++i) if (i < rev[i]) swap(a[i], a[rev[i]]); for (int i = 1; i < l; i <<= 1) { for (int j = 0; j < l; j += i << 1) { Com *l = a + j, *r = l + i, *wx = w + i, y; for (int k = 0; k < i; ++k, ++l, ++r, ++wx) { y = (*r) * (*wx); *r = (*l) - y; *l = (*l) + y; } } } } void Idft(Com *a, int l) { reverse(a + 1, a + l); Dft(a, l); for (int i = 0; i < l; ++i) { a[i].x /= l; a[i].y /= l; } } vector<double> Mul(vector<double> a, vector<double> b) { int n = a.size(), m = b.size(), l; for (l = 1; l < n + m - 1; l <<= 1); Init(l); a.resize(l), b.resize(l); for (int i = 0; i < l; ++i) { ta[i] = (Com){ a[i], 0 }; tb[i] = (Com){ b[i], 0 }; } Dft(ta, l), Dft(tb, l); for (int i = 0; i < l; ++i) { ta[i] = ta[i] * tb[i]; } Idft(ta, l); for (int i = 0; i < l; ++i) { a[i] = ta[i].x; } a.resize(n + m - 1); return a; } } void Cut(vector<double> &a, int &base) { const double GAMA = 1e-9; int id = 0; while (a[id] < GAMA) ++id; for (int i = 0; i + id < a.size(); ++i) { a[i] = a[i + id]; } while (a.back() < GAMA) { a.pop_back(); } base += id; } int main() { int tc; for (cin >> tc; tc--; ) { int x, y; cin >> x >> y; vector<double> ans(x * y); vector<double> F(x, 1.0 / x), G(1, 1); int base_f = 0, base_g = 0; for (int ex = y; ex; ex >>= 1) { if (ex & 1) { G = PO::Mul(G, F); base_g += base_f; Cut(G, base_g); } F = PO::Mul(F, F); base_f *= 2; Cut(F, base_f); } for (int i = 0; i < G.size(); ++i) { ans[i + base_g] = G[i]; } for (int i = 1; i < x * y; ++i) { ans[i] += ans[i - 1]; } for (int cas = 10; cas--; ) { int l, r; cin >> l >> r; printf("%.12f\n", ans[r] - (l? ans[l - 1] : 0)); } } return 0; }
View Code
相关文章
相关标签/搜索