@loj - 3160@「NOI2019」斗主地
@description@
小 S 在和小 F 玩一个叫「斗地主」的游戏。
可怜的小 S 发现自己打牌并打不过小 F,所以他想要在洗牌环节动动手脚。
一副牌一共有 n 张牌,从上到下依次标号为 1~n 。标号为 \(i\) 的牌分数是 \(f(i)\)。在本题,\(f(i)\) 有且仅有两种可能:\(f(i) = i\) 或 \(f(i) = i^2\)。
洗牌的方式和我们日常生活中的比较类似,以下我们用形式化的语言来定义:
洗牌环节一共分 m 轮,这 m 轮洗牌依次进行。第 i 轮洗牌时:
- 小 S 会拿出从最上面往下数的前 \(A_i\) 张牌。这样这副牌就被分成了两堆:第一堆是最上面的 \(A_i\) 张牌,第二堆是剩下的 \(n - A_i\) 张牌,且这两堆牌内相对顺序不变。特别地,当 \(A_i = n\) 或 \(A_i = 0\) 时,有一堆牌是空的。
- 接下来对两堆牌进行合并,从而产生新的第三堆牌。当第一堆牌还剩下 \(X\) 张,第二堆牌还剩下 \(Y\) 张的时候,以 \(\frac{X}{X + Y}\) 的概率取出第一堆牌的最下面的牌,并将它放入新的第三堆牌的最上面,\(\frac{Y}{X + Y}\) 的概率取出第二堆牌的最下面的牌,并将它放入新的第三堆牌的最上面。
- 重复操作 2,一直取到两堆牌都为空为止。这样我们就完成了一轮洗牌。
因为洗牌过程是随机的,所以小 S 发现自己没法知道某个位置上具体是哪张牌。但小 S 想问你在经历了这 m 轮洗牌后,某个位置上的牌的期望分数是多少。小 S 一共会问你 Q 个这样的问题。
@solution@
分析一下,洗牌操作就是不改变两堆牌的内部顺序,随机从 \({n\choose A}\) 种合法方案中选择一种。
先看 \(type = 1\)。经过打表差分 + 目瞪口呆观察法可得,无论洗牌多少次,期望值与位置呈一次函数关系。
再看 \(type = 2\)。虽然目瞪口呆看不出来,但是联系 \(type=1\) 不难猜测期望值与位置呈二次函数关系。经过二次差分 + 再次目瞪口呆观察法可以验证的确如此。
然后就随便维护 3 张牌,拉格朗日插值即可。这道题就从noi难度变成了noip难度。
证明?虽然你不证明也能过,不过我们还是来理性愉悦一下吧。
不妨假设 \(n < m\) 与 \(m < 0\) 时都有 \({n\choose m} = 0\),写式子:
我们取 \(\sum_{i=1}^{A}{x-1\choose i-1}{n-x\choose A-i}\) 部分继续化简:
当然你可以从组合意义更快地得到该式子,展示代数过程是为了方便下面的推广:
类似地,你也可以证明:
对于后面 \(\sum_{i=1}^{n-A}{x-1\choose i-1}{n-x\choose n-A-i}f_{i+A}\) 的部分,采取类似的证法也可以得到类似的结论。
也就是说洗牌的过程中期望的最高次数不会改变。一开始是 k 次函数,后面依然是 k 次函数。
@accepted code@
#include <cstdio>
#include <algorithm>
using namespace std;
const int MOD = 998244353;
const int MAXN = 10000000;
inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
inline int mul(int x, int y) {return (int)(1LL * x * y % MOD);}
int pow_mod(int b, int p) {
int ret = 1;
for(int i=p;i;i>>=1,b=mul(b,b))
if( i & 1 ) ret = mul(ret, b);
return ret;
}
int read() {
int x = 0, ch = getchar();
while( ch > '9' || ch < '0' ) ch = getchar();
while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
return x;
}
void write(int x) {
if( x / 10 ) write(x / 10);
putchar(x % 10 + '0');
}
int iv[MAXN + 5];
void init() {
iv[1] = 1; for(int i=2;i<=MAXN;i++) iv[i] = sub(0, mul(MOD / i, iv[MOD % i]));
}
int inv(int x) {
if( x <= MAXN ) return iv[x];
else if( sub(0, x) <= MAXN ) return sub(0, iv[sub(0, x)]);
else return pow_mod(x, MOD - 2);
}
struct point{int x, y;};
int get(point a, point b, int x) {
int p1 = inv(sub(a.x, b.x)), p2 = inv(sub(b.x, a.x));
return add(mul(a.y, mul(sub(x, b.x), p1)), mul(b.y, mul(sub(x, a.x), p2)));
}
int get(point a, point b, point c, int x) {
int p1 = mul(inv(sub(a.x, b.x)), inv(sub(a.x, c.x))), q1 = mul(sub(x, b.x), sub(x, c.x));
int p2 = mul(inv(sub(b.x, c.x)), inv(sub(b.x, a.x))), q2 = mul(sub(x, c.x), sub(x, a.x));
int p3 = mul(inv(sub(c.x, a.x)), inv(sub(c.x, b.x))), q3 = mul(sub(x, a.x), sub(x, b.x));
return add(add(mul(a.y, mul(p1, q1)), mul(b.y, mul(p2, q2))), mul(c.y, mul(p3, q3)));
}
int ans[MAXN + 5];
int main() {
freopen("landlords.in", "r", stdin);
freopen("landlords.out", "w", stdout);
init(); int n = read(), m = read(), type = read();
if( type == 1 ) {
if( n == 1 ) ans[1] = 1;
else {
point a = (point){1, 1}, b = (point){n, n};
for(int i=1;i<=m;i++) {
int A = read();
if( A == 0 || A == n ) continue;
int t1 = add(mul(get(a, b, 1), A), mul(get(a, b, A + 1), n - A));
int t2 = add(mul(get(a, b, A), A), mul(get(a, b, n), n - A));
a = (point){1, mul(t1, inv(n))}, b = (point){n, mul(t2, inv(n))};
}
for(int i=1;i<=n;i++) ans[i] = get(a, b, i);
}
} else {
if( n == 1 ) ans[1] = 1;
else if( n == 2 ) {
bool flag = false;
for(int i=1;i<=m;i++)
flag |= (read() == 1);
if( flag ) ans[1] = ans[2] = mul(5, inv(2));
else ans[1] = 1, ans[2] = 4;
} else {
point a = (point){1, 1}, b = (point){2, 4}, c = (point){n, mul(n, n)};
for(int i=1;i<=m;i++) {
int A = read();
if( A == 0 || A == n ) continue;
int p1 = (A == 1) ? 0 : mul(get(a, b, c, 2), mul(A, A - 1));
int p2 = mul(get(a, b, c, 1), mul(A, n - A));
int p3 = mul(get(a, b, c, A + 1), mul(A, n - A));
int p4 = (A == n - 1) ? 0 : mul(get(a, b, c, A + 2), mul(n - A, n - A - 1));
int t1 = add(mul(get(a, b, c, 1), A), mul(get(a, b, c, A + 1), n - A));
int t2 = add(mul(get(a, b, c, A), A), mul(get(a, b, c, n), n - A));
a = (point){1, mul(t1, inv(n))}, b = (point){n, mul(t2, inv(n))};
c = (point){2, mul(add(add(p1, p2), add(p3, p4)), mul(inv(n), inv(n - 1)))};
}
for(int i=1;i<=n;i++) ans[i] = get(a, b, c, i);
}
}
for(int Q=read();Q;Q--)
write(ans[read()]), puts("");
}
@details@
真就打表就能win啊。
为什么我去年会在考试时一直想生成函数怎么写啊。
实现时可以把一次函数与二次函数合在一起写。
话说都没有人写出题人给的那个 “倒着看是基数排序” 的做法吗。update in 2020/06/09:lk orz