【香蕉OI】扫雷(概率期望)

题意

如题,你在玩扫雷。有些格子是雷,不是雷的格子有一个数字,表示他周围 8 个格子里有多少雷。

有一个 n ∗ m n*m nm 的游戏界面。一共有 w w w 个雷,其中有 k k k 个已经确定,其他的所有 C n ∗ m − k w − k C_{n*m-k}^{w-k} Cnmkwk 种布雷方案概率相等。

求所有不是雷的格子的数字之和的期望和方差。 ( n ∗ m ≤ 4 ∗ 1 0 5 ) (n*m\le 4*10^5) (nm4105)

思路

a i , j a_{i,j} ai,j 为一个随机变量,表示 ( i , j ) (i,j) (i,j) 格子里的数字。

首先考虑期望。因为

E ( ∑ a i , j ) = ∑ E ( a i , j ) E(\sum a_{i,j})=\sum E(a_{i,j}) E(ai,j)=E(ai,j)

所以直接对于每个格子求期望数字就好了。做法是直接枚举周围 8 格中有多少雷,排列组合。

然后考虑方差。方差等于平方的期望减去期望的平方

V ( ∑ a i , j ) = E [ ( ∑ a i , j ) 2 ] − E ( ∑ a i , j ) 2 = ∑ ∑ E ( a i , j ⋅ a k , l ) − ( ∑ E ( a i , j ) ) 2 \begin{aligned} &V(\sum a_{i,j}) \\=&E[(\sum a_{i,j})^2]-E(\sum a_{i,j})^2 \\=&\sum \sum E(a_{i,j}\cdot a_{k,l})-(\sum E(a_{i,j}))^2 \end{aligned} ==V(ai,j)E[(ai,j)2]E(ai,j)2E(ai,jak,l)(E(ai,j))2

期望的平方上面已经求过了,现在考虑如何求平方的期望。

因为有雷的总数限制,所以任意两个格子显然都不是独立的。

分为以下两种情况考虑:

  1. 两个格子在同一个位置
  2. 两个格子不在同一个位置

对于第一种,在计算期望的时候顺便求一下就好了,只要把每次的贡献平方就行。

对于第二种情况,考虑设计这样一个状态来表示两个格子的数字相乘的期望,如下:

( p u b _ c a n , p u b _ m i n e , c a n 0 , m i n e 0 , c a n 1 , m i n e 1 ) (pub\_can, pub\_mine, can0, mine0, can1, mine1) (pub_can,pub_mine,can0,mine0,can1,mine1)

其中 c a n can can 表示可以放雷的空格子个数, m i n e mine mine 表示已经确定的雷的个数。然后 p u b _ c a n , p u b _ m i n e pub\_can,pub\_mine pub_can,pub_mine 表示的是两个格子共有的部分, 即两个格子所在的九宫格的交集,后面的 0 , 1 0,1 0,1 分别表示两个格子私有的部分。确定这 6 个变量的状态之后,只要枚举公共部分放几个雷,两个格子的私有部分放几个雷,就可以用与求期望类似的方法求出了。(好像题解里有神仙有 5 维的状态,然而我不会)

然后可以发现,与 ( i , j ) (i,j) (i,j) 有公共部分的最多只有 24 个格子,暴力枚举这 24 个格子。对于其他的格子,只要可以快速查询状态为 c a n 1 , m i n e 1 can1, mine1 can1,mine1 的格子有多少个的话,也可以统计。

方差好像一般就是这个套路,在求平方的期望的时候处理一下随机变量互相不独立的情况。

这里悄咪咪膜一下 fyy2603 ,代码清晰,我抄的很爽

注意

  1. 因为只有 n ∗ m ≤ 4 ∗ 1 0 5 n*m\le 4*10^5 nm4105 的限制,而 n , m n,m n,m 可能一大一小,要注意内存。
    可以用 #define f(i, j) f[(i-1)*m+j] 来避免内存的问题。
  2. 因为对于某一个状态,他的贡献是一样的,所以可以先计数,再统一计算答案,可以避免一些不必要的计算。

代码

人傻常数大,要开优化才能过。

#pragma GCC optimize("2", "Ofast", "inline")
#include<bits/stdc++.h>
#define re register
#define pii pair<int, int>
#define fi first
#define se second
#define mp make_pair
#define tag(i, j) tag[(i-1)*m+j]
#define c(i, j) c[(i-1)*m+j]
#define vis(i, j) vis[(i-1)*m+j]
using namespace std;
typedef long long LL;
const int dx[24] = {1, -1, 0, 0, 1, -1, 1, -1, 2, -2, 0, 0, 2, -2, 2, -2, 2, -2, 2, -2, 1, -1, 1, -1};
const int dy[24] = {0, 0, 1, -1, 1, 1, -1, -1, 0, 0, 2, -2, 2, 2, -2, -2, 1, 1, -1, -1, 2, 2, -2, -2};
const int N = 4e5 + 10, mod = 998244353;
int n, m, w, K;
bool tag[N], vis[N];
int itot, fac[N], ifac[N];
int a[9][9], b[9][9][9][9][9][9];
pii c[N];
int sume, sumf;

template<class T>inline void read(T &x){
	x = 0; bool fl = 0; char c = getchar();
	while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
	while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
	if (fl) x = -x;
}

namespace Combination
{
	inline void add(int &x, int y){x += y; if (x >= mod) x -= mod;}
	inline int fpow(int x, int y){
		int ret = 1;
		while (y){
			if (y&1) ret = 1LL*ret*x%mod;
			x = 1LL*x*x%mod;
			y >>= 1;
		}
		return ret;
	}
	inline int inv(int x){
		return fpow(x, mod-2);
	}
	inline int C(int x, int y){
		if (x < 0 || y < 0 || x < y) return 0;
		return 1LL*fac[x]*ifac[y]%mod*ifac[x-y]%mod;
	}
	void init(){
		fac[0] = 1;
		for (re int i = 1; i < N; ++ i)
			fac[i] = 1LL*i*fac[i-1]%mod;
		ifac[N-1] = inv(fac[N-1]);
		for (re int i = N-2; i >= 0; -- i)
			ifac[i] = 1LL*ifac[i+1]*(i+1)%mod;
		itot = inv(C(n*m-K, w-K));
	}
}
using namespace Combination;

inline bool ok(int x, int y){
	return x >= 1 && x <= n && y >= 1 && y <= m;
}

void get_a(){
	for (int i = 1; i <= n; ++ i)
		for (int j = 1; j <= m; ++ j){
			if (tag(i, j)) continue;
			for (int k = 0; k < 8; ++ k){
				int ni = i+dx[k], nj = j+dy[k];
				if (!ok(ni, nj)) continue;
				if (tag(ni, nj)) c(i, j).fi++;
				else c(i, j).se++;
			}
			a[c(i, j).fi][c(i, j).se]++;
		}
}

inline void upd_b(int xa, int ya, int xb, int yb){
	int q = 0, w = 0, e = 0, r = 0, t = 0, y = 0;
	for (re int k = 0; k < 8; ++ k){
		int nx = xa+dx[k], ny = ya+dy[k];
		if (!ok(nx, ny) || (nx == xb && ny == yb)) continue;
		vis(nx, ny) = 1;
		if (tag(nx, ny)) e++;
		else r++;
	}
	for (re int k = 0; k < 8; ++ k){
		int nx = xb+dx[k], ny = yb+dy[k];
		if (!ok(nx, ny) || (nx == xa && ny == ya)) continue;
		if (vis(nx, ny)){
			if (tag(nx, ny)) q++, e--;
			else w++, r--;
		}
		else{
			if (tag(nx, ny)) t++;
			else y++;
		}
	}
	for (re int k = 0; k < 8; ++ k){
		int nx = xa+dx[k], ny = ya+dy[k];
		if (!ok(nx, ny) || (nx == xb && ny == yb)) continue;
		vis(nx, ny) = 0;
	}
	b[q][w][e][r][t][y]++;
}

void get_b(){
	for (re int i = 1; i <= n; ++ i)
		for (re int j = 1; j <= m; ++ j){
			if (tag(i, j)) continue;
			for (int k = 0; k < 24; ++ k){
				int ni = i+dx[k], nj = j+dy[k];
				if (!ok(ni, nj) || tag(ni, nj)) continue;
				a[c(ni, nj).fi][c(ni, nj).se]--;
			}
			a[c(i, j).fi][c(i, j).se]--;
			for (int k = 0; k <= 8; ++ k)
				for (int o = 0; o <= 8; ++ o){
					add(b[0][0][c(i, j).fi][c(i, j).se][k][o], a[k][o]);
				}
			for (int k = 0; k < 24; ++ k){
				int ni = i+dx[k], nj = j+dy[k];
				if (!ok(ni, nj) || tag(ni, nj)) continue;
				a[c(ni, nj).fi][c(ni, nj).se]++;
			}
			a[c(i, j).fi][c(i, j).se]++;
			for (int k = 0; k < 24; ++ k){
				int ni = i+dx[k], nj = j+dy[k];
				if (!ok(ni, nj) || tag(ni, nj)) continue;
				upd_b(i, j, ni, nj);
			}
		}
}

void get_e(){
	for (int mine = 0; mine <= 8; ++ mine)
		for (int can = 0; can <= 8; ++ can){
			if (!a[mine][can]) continue;
			int cont = 0, cont2 = 0;
			for (int i = 0; i <= min(can, w-K); ++ i){
				add(cont, 1LL*C(can, i)*C(n*m-K-can-1, w-K-i)%mod*(i+mine)%mod);
				add(cont2, 1LL*C(can, i)*C(n*m-K-can-1, w-K-i)%mod*(i+mine)%mod*(i+mine)%mod);
			}
			cont = 1LL*cont*itot%mod;
			add(sume, 1LL*a[mine][can]*cont%mod);
			cont2 = 1LL*cont2*itot%mod;
			add(sumf, 1LL*a[mine][can]*cont2%mod);
		}
}

void get_f(){
	for (re int pm = 0; pm <= 8; ++ pm)
		for (re int pc = 0; pc <= 8; ++ pc)
			for (re int m0 = 0; m0 <= 8; ++ m0)
				for (int c0 = 0; c0 <= 8; ++ c0)
					for (int m1 = 0; m1 <= 8; ++ m1)
						for (int c1 = 0; c1 <= 8; ++ c1){
							if (!b[pm][pc][m0][c0][m1][c1]) continue;
							int cont = 0;
							for (int i = 0; i <= pc; ++ i)
								for (int j = 0; j <= c0; ++ j)
									for (int k = 0; k <= c1; ++ k){
										if (i+j+k > w-K) continue;
										add(cont, 1LL*C(pc, i)*C(c0, j)*C(c1, k)%mod*C(n*m-K-pc-c0-c1-2, w-K-i-j-k)%mod*(pm+m0+i+j)%mod*(pm+m1+i+k)%mod);
									}
							cont = 1LL*cont*itot%mod;
							add(sumf, 1LL*b[pm][pc][m0][c0][m1][c1]*cont%mod);
						}
}

int main()
{
	read(n); read(m); read(w); read(K);
	init();
	for (int i = 1; i <= K; ++ i){
		int x, y;
		read(x); read(y);
		tag(x, y) = 1;
	}
	get_a();
	get_b();
	get_e();
	get_f();
	printf("%d %lld\n", sume, (sumf-1LL*sume*sume%mod+mod)%mod);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值