@loj


@description@

小 S 在和小 F 玩一个叫「斗地主」的游戏。

可怜的小 S 发现自己打牌并打不过小 F,所以他想要在洗牌环节动动手脚。

一副牌一共有 n 张牌,从上到下依次标号为 1~n 。标号为 (i) 的牌分数(f(i))。在本题,(f(i)) 有且仅有两种可能:(f(i) = i)(f(i) = i^2)

洗牌的方式和我们日常生活中的比较类似,以下我们用形式化的语言来定义:

洗牌环节一共分 m 轮,这 m 轮洗牌依次进行。第 i 轮洗牌时:

  1. 小 S 会拿出从最上面往下数的前 (A_i) 张牌。这样这副牌就被分成了两堆:第一堆是最上面的 (A_i) 张牌,第二堆是剩下的 (n - A_i) 张牌,且这两堆牌内相对顺序不变。特别地,当 (A_i = n)(A_i = 0) 时,有一堆牌是空的。
  2. 接下来对两堆牌进行合并,从而产生新的第三堆牌。当第一堆牌还剩下 (X) 张,第二堆牌还剩下 (Y) 张的时候,以 (frac{X}{X + Y}) 的概率取出第一堆牌的最下面的牌,并将它放入新的第三堆牌的最上面,(frac{Y}{X + Y}) 的概率取出第二堆牌的最下面的牌,并将它放入新的第三堆牌的最上面。
  3. 重复操作 2,一直取到两堆牌都为空为止。这样我们就完成了一轮洗牌。

因为洗牌过程是随机的,所以小 S 发现自己没法知道某个位置上具体是哪张牌。但小 S 想问你在经历了这 m 轮洗牌后,某个位置上的牌的期望分数是多少。小 S 一共会问你 Q 个这样的问题。

原题传送门。

@solution@

分析一下,洗牌操作就是不改变两堆牌的内部顺序,随机从 ({nchoose A}) 种合法方案中选择一种。

先看 (type = 1)。经过打表差分 + 目瞪口呆观察法可得,无论洗牌多少次,期望值与位置呈一次函数关系。

再看 (type = 2)。虽然目瞪口呆看不出来,但是联系 (type=1) 不难猜测期望值与位置呈二次函数关系。经过二次差分 + 再次目瞪口呆观察法可以验证的确如此。

然后就随便维护 3 张牌,拉格朗日插值即可。这道题就从noi难度变成了noip难度。


证明?虽然你不证明也能过,不过我们还是来理性愉悦一下吧。

不妨假设 (n < m)(m < 0) 时都有 ({nchoose m} = 0),写式子:

[g_x = frac{1}{nchoose A}[sum_{i=1}^{A}{x-1choose i-1}{n-xchoose A-i}f_i + sum_{i=1}^{n-A}{x-1choose i-1}{n-xchoose n-A-i}f_{i+A}] ]

我们取 (sum_{i=1}^{A}{x-1choose i-1}{n-xchoose A-i}) 部分继续化简:

[egin{aligned} sum_{i=1}^{A}{x-1choose i-1}{n-xchoose A-i} &= sum_{i=1}^{A}frac{(x-1)!(n-x)!}{(i-1)!(A-i)!(x-i)!(n-x-A+i)!} \ &= frac{(x-1)!(n-x)!}{(A-1)!(n-A)!} imessum_{i=1}^{A}{A-1choose i-1}{n-Achoose x-i} \ &= frac{(x-1)!(n-x)!}{(A-1)!(n-A)!} imes{n-1choose x-1} \ &= {n - 1choose A - 1} end{aligned} ]

当然你可以从组合意义更快地得到该式子,展示代数过程是为了方便下面的推广:

类似地,你也可以证明:

[egin{aligned} sum_{i=1}^{A}{x-1choose i-1}{n-xchoose A-i}(i-1)^{underline{k}} &={n - k - 1choose A - k - 1}(x-1)^{underline{k}} end{aligned} ]

对于后面 (sum_{i=1}^{n-A}{x-1choose i-1}{n-xchoose 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

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13067893.html