P4457 [BJOI2018]治疗之雨

P4457 [BJOI2018]治疗之雨

题意描述比较迷惑,剩下 \(m\) 个数是无穷的意思是任意操作对它们不产生影响,操作影响的,或者说影响选哪个数进行操作的,只有第一个数,所以当前的状态可以用第一个数的值来表示。

\(dp_i\) 表示第一个数为 \(i\) 时把它变为 \(0\) 的期望步数,可得:

\[\begin{aligned} &dp_0 = 0\\ &dp_i = \frac{1}{m+1} \times (\sum_{j=0}^{min(i+1, k)}[\binom{k}{j} \times (\frac{1}{m+1})^j \times (\frac{m}{m+1})^{k-j} \times dp_{i+1-j}]) \\ &+ \frac{m}{m+1} \times (\sum_{j=0}^{min(i, k)}[\binom{k}{j} \times (\frac{1}{m+1})^j \times (\frac{m}{m+1})^{k-j} \times dp_{i-j}]) + 1\\ &dp_n = \sum_{j=0}^{min(n, k)}[\binom{k}{j} \times (\frac{1}{m+1})^j \times (\frac{m}{m+1})^{k-j} \times dp_{n-j}]+1\\ \end{aligned} \]

\(dp_p\) 无解则减不到 \(0\),否则 \(dp_p\) 即为答案。

对于 \(dp_0\),显然不需要任何操作。

对于 \(dp_n\) 的转移,枚举第一个数一共减了 \(j\) 次以及具体在哪几次操作中减了第一个数,对于每一个减了 \(j\) 次的方案,有 \((\frac{1}{m+1})^j \times (\frac{m}{m+1})^{k-j}\) 的概率出现,又因一共有 \(\binom{k}{j}\) 种方案,所以把 \(n\) 减为 \(n-j\) 的概率为 \(\binom{k}{j} \times (\frac{1}{m+1})^j \times (\frac{m}{m+1})^{k-j}\)

对于 \(dp_i\) 的转移,类似于 \(dp_n\)。第一个数 \(i\)\(\frac{1}{m+1}\) 的概率变为 \(i+1\),有 \(\frac{m}{m+1}\) 的概率不变,分类以后两种状态的转移与 \(dp_n\) 的转移相同。

需注意,由于第一个数减为 \(0\) 后不能再减小,设当前第一个数为 \(i\),那么最多进行 \(min(i, k)\) 次减 \(1\) 操作。

\(\text{dp}\) 状态和转移设计好了,接下来思考怎么转移。发现转移有后效性,再看一眼数据范围,\(1 \leq p \leq n \leq 1500\),似乎可以用高斯消元?

但正常高斯消元复杂度是 \(O(n^3)\) 的,\(O(T \cdot n^3)\) 过不了。/kk

但观察转移方程或打印一下增广矩阵发现矩阵都是形如这样的:

\( \begin{bmatrix} k_{1,1} & k_{1,2} & ... & 0 & 0 & k_{1,n+1}\\ k_{2,1} & k_{2,2} & k_{2,3} & ... & 0 & k_{2,n+1}\\ ... & ... & ... & ... & ... & ...\\ 0 & k_{n-1,n-1-min(n-1,k)} & k_{n-1,n-min(n-1,k)} & ... & k_{n-1,n} & k_{n-1,n+1}\\ 0 & 0 & k_{n,n-min(n,k)} & ... & k_{n,n} & k_{n,n+1} \end{bmatrix} \)

看得出矩阵的左下和右上会存在一堆 \(0\)。当 \(k \ge n-1\) 时,共有右上角 \(\frac{(n-1)(n-2)}{2}\)\(0\)。从上往下消元,当前行最多只有 \(2\) 个数,于是高斯消元的复杂度可以降到 \(O(n^2)\)

具体实现时,由于 \(k\) 很大,但是是固定的,可以递推预处理出所有的概率系数。

设:

\[f(x)=\binom{k}{x} \times (\frac{1}{m+1})^x \times (\frac{m}{m+1})^{k-x} \]

展开得:

\[f(x) = \frac{k!}{x!(k-x)!} \times \frac{m^{k-x}}{(m+1)^k} \]

\(f(x)\) 递推式为:

\[f(x) = \frac{(k-x+1) \times f(x-1)}{m x} \]

起始项:

\[f(0) = (\frac{m}{m+1})^k \]

还有一些小细节,\(k=0\)\(p \ne 0\) 时无解,\(m=0\) 时直接计算,高斯消元第二层函数里不能有求逆元操作,否则复杂度多一个 \(\text{log}\)

整体复杂度 \(O(T \cdot n^2)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll SIZE = 1505;
const ll mod = 1000000007;
ll a[SIZE][SIZE];
ll f[SIZE];
ll n, st, m, K;
ll T;

ll power(ll x, ll y){
	ll jl = 1;
	while(y){
		if(y & 1) jl = (jl * x) % mod;
		x = (x * x) % mod;
		y >>= 1;
	}
	return jl;
}

int main(){
	scanf("%lld", &T);
	while(T--){
		scanf("%lld%lld%lld%lld", &n, &st, &m, &K);
		//特判 
		if(K == 0){
			printf("-1\n");
			continue;
		}
		if(m == 0){
			if(K <= 1) printf("-1\n");
			else{
				ll cnt = 0;
				while(st > 0){
					if(st < n) st++;
					st -= K;
					cnt++;
				}
				printf("%lld\n", cnt);
			}
			continue;
		}
		//构造增广矩阵 
		ll t = power(m+1, mod-2), tt = ((1-t)%mod+mod)%mod;
		for(ll i = 1; i <= n; i++){
			for(ll j = 1; j <= n+1; j++){
				a[i][j] = 0;
			}
			f[i] = 0;
		}
		ll xx;
		f[0] = power((m * t) % mod, K);
		for(ll i = 1; i <= n; i++){
			f[i] = ((((K-i+1+mod)%mod) * f[i-1])%mod * power((m*i)%mod, mod-2)) % mod;
		}
		for(ll i = 1; i < n; i++){
			a[i][i] = (-1+mod)%mod;
			for(ll j = 0; j <= min(i+1, K); j++){
				xx = (f[j] * t) % mod;
				a[i][i+1-j] = (a[i][i+1-j] + xx) % mod;
			}
			for(ll j = 0; j <= min(i, K); j++){
				xx = (f[j] * tt) % mod;
				a[i][i-j] = (a[i][i-j] + xx) % mod;
			}
			a[i][n+1] = (-1+mod)%mod;
		}
		a[n][n] = mod-1;
		for(ll j = 0; j <= min(n, K); j++){
			a[n][n-j] = (a[n][n-j] + f[j]) % mod;
		}
		a[n][n+1] = mod-1;
		//高斯消元 
		for(ll i = 1; i <= n; i++){
			ll uu = power(a[i][i], mod-2);
			for(ll j = i+1; j <= n; j++){
				xx = (a[j][i] * uu) % mod;
				for(ll k = i; k <= n; k++){
					if(a[i][k] == 0) break;
					a[j][k] = (a[j][k] - ((xx * a[i][k]) % mod) + mod) % mod;
				}
				a[j][n+1] = (a[j][n+1] - ((xx * a[i][n+1]) % mod) + mod) % mod;
			}
		}
		for(ll i = n-1; i >= 1; i--){
			xx = (a[i][i+1] * power(a[i+1][i+1], mod-2)) % mod;
			a[i][i+1] = 0;
			a[i][n+1] = (a[i][n+1] - ((xx*a[i+1][n+1])%mod) + mod) % mod;	
		}
		//判无解 
		bool ff = 1;
		for(int i = 1; i <= n; i++){
			if(a[i][i] == 0 && a[i][n+1] != 0){
				ff = 0;
				break;
			}
		}
		if(!ff) printf("-1\n");
		else{
			ll ans = (a[st][n+1] * power(a[st][st], mod-2)) % mod;
			printf("%lld\n", ans);	
		}
	}
	return 0;
}

posted @ 2023-07-09 08:39  Semorius  阅读(52)  评论(0)    收藏  举报