1. 程式人生 > 其它 >NOI 2019 題目選做

NOI 2019 題目選做

鬥主地

題目描述

點此看題

解法

首先考慮 \(30\) 分的做法,我們可以設計 \(f[i][j]\) 表示前 \(i\) 輪第 \(j\) 個位置的期望分數,\(g[i][j]\) 表示對於現在這一輪的 \(a\),第一堆取走了 \(i\) 個,第二堆取走了 \(j\) 個的概率,轉移很容易寫。

結論是:一次函式洗牌之後的期望仍然是一次函式,二次函式洗牌後的期望仍然是二次函式由於我的數學功底太差,所以並不能證明這個結論。知道這個結論以後我們用 \(dp\) 維護前三項然後插值即可,時間複雜度 \(O(n+9\cdot m)\)

#include <cstdio>
#include <cstring>
const int M = 105;
#define int long long
const int MOD = 998244353;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,q,w,tp,A,B,C,f[5],l[5],r[5],g[5][5],inv[10000005];
void add(int &x,int y) {x=(x+y)%MOD;}
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
int ask(int x)
{
	return (A*x%MOD*x+B*x+C)%MOD;
}
signed main()
{
	n=read();m=read();tp=read();
	if(tp==1) A=0,B=1,C=0;
	if(tp==2) A=1,B=0,C=0;
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++)
		inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=m;i++)
	{
		int x=read(),y=n-x;w^=1;
		for(int j=1;j<=3;j++)
			l[j]=ask(j),r[j]=ask(j+x);
		memset(f,0,sizeof f);
		memset(g,0,sizeof g);g[0][0]=1;
		for(int j=0;j<=x && j<=3;j++)
			for(int k=0;k<=y && k<=3;k++)
			{
				int v=inv[x-j+y-k];
				if(j<x) add(g[j+1][k],g[j][k]*v%MOD*(x-j));
				if(k<y) add(g[j][k+1],g[j][k]*v%MOD*(y-k));
			}
		for(int j=1;j<=n && j<=3;j++)
			for(int k=1;k<=j;k++)
			{
				int v=inv[n-j+1];
				if(k<=x) add(f[j],l[k]*g[k-1][j-k]%MOD*(x-k+1)%MOD*v);
				if(k<=y) add(f[j],r[k]*g[j-k][k-1]%MOD*(y-k+1)%MOD*v);
			}
		A=((f[3]-2*f[2]+f[1])*inv[2]%MOD+MOD)%MOD;
		B=((8*f[2]-5*f[1]-3*f[3])*inv[2]%MOD+MOD)%MOD;
		C=((3*f[1]-3*f[2]+f[3])%MOD+MOD)%MOD;
	}
	q=read();
	while(q--) printf("%lld\n",ask(read()));
}