其他分享
首页 > 其他分享> > 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()));
}

标签:选做,NOI,int,inv,tp,read,2019,一次函数,MOD
来源: https://www.cnblogs.com/C202044zxy/p/16245004.html