123789456ye

已AFO

[BZOI2018]治疗之雨

题面:Luogu
题解:期望dp+gauss消元
先吐槽一下这个题面,根本看不懂,看讨论区半天才发现,另外m个就是用来降低概率的
\(prob_i\)表示一轮降了\(i\)点的概率

\[Prob_i=\frac{\binom{k}{i}m^{k-i}}{(m+1)^k} \]

总共\((m+1)^{k}\)种方案,选\(i\)次降一点,剩下的不降
由于\(k\)很大,所以\(\binom{k}{i}\)不能通过预处理阶乘算出,而要递推(否则会#6~#10RE)
递推方法很多,我的是

\[prob_i=\frac{1}{i!}*m^{k-i}*\frac{k!}{(k-i)!}*\frac{1}{(m+1)^k} \]

\(Q_{x,y}\)表示一轮操作从\(x\)点变成\(y\)

\[Q_{x,y}=\begin{split} \left\{ \begin{array}{lr} 0 & x=n \&\& y=n+1 &\\ Prob_{x-y} & x=n\\ \frac{1}{m+1}Prob_0 & y=x+1\\ \frac{m}{m+1}Prob_{x-y}+\frac{1}{m+1}Prob_{x-y+1} & \text{otherwise} \end{array} \right. \end{split} \]

\(dp_i\)表示从\(i\)点变成\(0\)的期望步数

\[\begin{split} & dp_i=1+\sum_{j=1}^{i+1} Q_{i,j}dp_j & (1\le i<n) \\ & dp_n=1+\sum_{j=1}^nQ_{n,j}dp_j \end{split} \]

于是可以推一下式子

\[dp_{i+1}=\frac{dp_i-1-\sum_{j=1}^{i}{Q_{i,j}dp_{j}}}{Q_{i,i+1}} (1\le i<n) \]

可以发现,我们只需求出\(dp_1\)即可求出其它
然后是两种办法
1.高斯消元
矩阵大概是这样

\[\left[ \begin{matrix} 1-Q_{1,1} & -Q_{1,2} & 0 &\cdots & 0\\ -Q_{2,1} & 1-Q_{2,2} & -Q_{2,3} &\cdots & 0 \\ \vdots & \vdots & \vdots &\ddots & \vdots \\ -Q_{n-1,1} & -Q_{n-1,2} & -Q_{n-1,3} &\cdots & -Q_{n-1,n} \\ -Q_{n,1} & -Q_{n,2} & -Q_{n,3} &\cdots & 1-Q_{n,n} \end{matrix} \right]* \left[ \begin{matrix} dp_{1}&\\ dp_{2}&\\ \vdots&\\ dp_{n-1}&\\ dp_{n}& \end{matrix} \right]= \left[ \begin{matrix} 1&\\ 1&\\ \vdots&\\ 1&\\ 1& \end{matrix} \right] \]

然后可以通过一些手法把它搞成只要\(n^2\)

2.设\(dp_1=X\)
则每一项都可以表示为\(A[i]*X+B[i]\)
然后通过\(dp_n\)的递推式也能得到一个\(dp_n=An*X+Bn\)
所以

\[A[n]*X+B[n]=An*X+Bn \\ X=\frac{Bn-B[n]}{A[n]-An} \]

然后带回去就可以了
我也是写的这种方法
还有一件令我迷惑的事:prob每次不清零会WA#1~#5,但是我写的每次应该都会重新算一遍,在没被改变的地方应该也没有被调用

#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T& x)
{
	x = 0; char c = getchar();
	while (!isdigit(c)) c = getchar();
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 1505
#define P 1000000007
int fac[maxn], inv[maxn], prob[maxn];
int A[maxn], B[maxn], An, Bn, X;
int n, p, m, k, invM;
inline int mul(int x, int y) { return 1ll * x * y % P; }
inline int qpow(int x, int y)
{
	int ans = 1;
	for (; y; x = mul(x, x), y >>= 1) if (y & 1) ans = mul(ans, x);
	return ans;
}
inline int getp(int x, int y)//Q_{x,y}
{
	if (x == n && y == n + 1) return 0;
	if (x == n) return prob[x - y];
	if (y == x + 1) return mul(invM, prob[0]);
	return (0ll + mul(mul(m, prob[x - y]), invM) + mul(invM, prob[x - y + 1])) % P;
}
inline void exgcd(int a, int b, int& x, int& y)
{
	if (!b) { x = 1, y = 0; return; }
	exgcd(b, a % b, y, x);
	y -= a / b * x;
}
inline int getinv(int a)
{
	int x, y;
	exgcd(a, P, x, y);
	x %= P;
	return (x < 0) ? x + P : x;
}
inline void init(int maxnum = 1500)
{
	fac[0] = inv[0] = 1;
	for (int i = 1; i <= maxnum; ++i) fac[i] = 1ll * fac[i - 1] * i % P;
	inv[maxnum] = getinv(fac[maxnum]);
	for (int i = maxnum - 1; i; --i) inv[i] = 1ll * inv[i + 1] * (i + 1) % P;
}
inline void getprob()
{
	invM = getinv(m + 1);
	int inv1 = qpow(invM, k);
	for (int i = 0, tp = qpow(m, k), tp2 = getinv(m), tpm = 1; i <= min(n, k); ++i)
		prob[i] = mul(mul(mul(inv[i], tpm), tp), inv1), tp = mul(tp, tp2), tpm = mul(tpm, k - i);
	//tp=m^{k-i},inv[i]=\frac{1}{i!},tpm=\frac{k!}{(k-i)!},inv1=\frac{1}{(m+1)^k}
}
inline void solve()
{
	A[1] = 1, B[1] = 0; An = 0, Bn = 1;
	for (int i = 2; i <= n; ++i)
	{
		A[i] = A[i - 1], B[i] = B[i - 1] - 1;
		for (int j = 1, tp; j < i; ++j)
		{
			tp = getp(i - 1, j);
			A[i] = (0ll + A[i] - mul(tp, A[j])) % P;
			B[i] = (0ll + B[i] - mul(tp, B[j])) % P;
		}
		if (A[i] < 0) A[i] += P;
		if (B[i] < 0) B[i] += P;
		int tp = getinv(getp(i - 1, i));
		A[i] = mul(A[i], tp), B[i] = mul(B[i], tp);
	}
	for (int i = 1, tp; i <= n; ++i)
	{
		tp = getp(n, i);
		An = (0ll + An + mul(tp, A[i])) % P;
		Bn = (0ll + Bn + mul(tp, B[i])) % P;
	}
	X = mul((Bn - B[n] + P) % P, getinv((A[n] - An + P) % P));
	printf("%d\n", (mul(A[p], X) + B[p]) % P);
	memset(prob, 0, sizeof(prob));
}
int main()
{
	int T; read(T);
	init();
	while (T--)
	{
		read(n), read(p), read(m), read(k);
		if (!k || (!m && k == 1)) { puts("-1"); continue; }//特判
		if (!m)
		{
			int ans = 0;
			while (p > 0)
			{
				if (p < n) ++p;
				p -= k, ++ans;
			}
			printf("%d\n", ans);
			continue;
		}
		getprob();
		solve();
	}
	return 0;
}
posted @ 2020-03-21 10:10  123789456ye  阅读(135)  评论(0编辑  收藏  举报