[unknown OJ] ZZH与计数

一、题目

点此看题

二、解法

这题我一开始是想的设 d p [ i ] dp[i] dp[i] 为数 i i i 有多少个的期望,然后跑矩阵乘法可以做到 O ( 2 3 n log ⁡ m ) O(2^{3n}\log m) O(23nlogm)

上述做法无法优化,行不通。首先要对题目有一个基本的转化: p p p 的概率使得每一个 1 1 1 1 2 \frac{1}{2} 21 的概率变成 0 0 0,有 1 − p 1-p 1p 的概率使得每一个 0 0 0 1 2 \frac{1}{2} 21 变成 1 1 1

然后发现这个转移与数位有关的,我们考虑能不能只以 0 / 1 0/1 0/1 的个数来表示他,我自己 y y \tt yy yy 了一种思路,设 d p [ t ] [ i ] dp[t][i] dp[t][i] 为经过了 t t t 轮后 1 1 1 的个数变成了 i i i,由于要得到一开始的概率(这里由于不能确定数是什么只能算概率,最后再算期望),我们可以枚举一开始 1 1 1 的个数。

但是是错的,举个例子 m = 0 m=0 m=0 时,假设一开始只有 10 10 10,但是 10 10 10 01 01 01 的概率都是 1 1 1,显然是不可能的。究其原因,其实就是归类错误,这里我们一定要体现变化量才行(这样才能区分嘛),上面的状态只存了 1 1 1 的个数就是错误的。


沿用上面的方法,我们先枚举 i 0 i_0 i0 为一开始为 0 0 0 的个数, j 0 = n − i 0 j_0=n-i_0 j0=ni0 就是一开始为 1 1 1 的个数。设 f [ t ] [ i ] [ j ] f[t][i][j] f[t][i][j] 为经过了 t t t 轮,一开始为 0 0 0 的变成了 i i i 1 1 1,一开始为 1 1 1 的还剩 j j j 1 1 1,我们来写转移。

第一种: 0 0 0 1 1 1 概率 1 − p 1-p 1p,我们枚举 f [ t − 1 ] [ x ] [ y ] f[t-1][x][y] f[t1][x][y],那么他转移过来首先要乘上 ( 1 2 ) n − x − y (\frac{1}{2})^{n-x-y} (21)nxy 也就是我们先让这些都变化成特定的一种,感觉还要乘上一个组合数(注意:这个状态的定义是一种特定情况的概率,他们是相同的所以才放在一个 d p dp dp 数组里面),所以这个组合数是 C ( i , x ) × C ( j , y ) C(i,x)\times C(j,y) C(i,x)×C(j,y) 也就是看 f [ t ] [ i ] [ j ] f[t][i][j] f[t][i][j] 能从几个 f [ t − 1 ] [ x ] [ y ] f[t-1][x][y] f[t1][x][y] 中转移过来。

第二种: 1 1 1 0 0 0 概率 p p p,同理可得: ( 1 2 ) x + y C ( i 0 − i , x − i ) × C ( j 0 − j , y − j ) (\frac{1}{2})^{x+y}C(i_0-i,x-i)\times C(j_0-j,y-j) (21)x+yC(i0i,xi)×C(j0j,yj),那么总的转移方程:
f [ t ] [ i ] [ j ] = ∑ x = 0 i ∑ y = 0 j ( 1 2 ) n − x − y × C ( i , x ) C ( j , y ) × f [ t − 1 ] [ i ] [ j ] f[t][i][j]=\sum_{x=0}^{i}\sum_{y=0}^{j}(\frac{1}{2})^{n-x-y}\times C(i,x)C(j,y)\times f[t-1][i][j] f[t][i][j]=x=0iy=0j(21)nxy×C(i,x)C(j,y)×f[t1][i][j] + ∑ x = i i 0 ∑ y = j j 0 ( 1 2 ) x + y × C ( i 0 − i , x − i ) C ( j 0 − j , y − j ) × f [ t − 1 ] [ i ] [ j ] +\sum_{x=i}^{i_0}\sum_{y=j}^{j_0}(\frac{1}{2})^{x+y}\times C(i_0-i,x-i)C(j_0-j,y-j)\times f[t-1][i][j] +x=ii0y=jj0(21)x+y×C(i0i,xi)C(j0j,yj)×f[t1][i][j]显然上面的转移是可以矩阵快速幂的,时间复杂度 O ( n 7 log ⁡ m ) O(n^7\log m) O(n7logm),但是由于状态只会有 n 2 / 2 n^2/2 n2/2 个,那么常数是 1 64 \frac{1}{64} 641,所以算概率的部分我们成功通过了。


概率要乘上贡献,但是如果我们暴力算的话时间复杂度 O ( 2 2 n ) O(2^{2n}) O(22n) 。这部分可以通过打标记的方法优化,期望是 v × F [ i 0 ] [ i ] [ j ] v\times F[i_0][i][j] v×F[i0][i][j],也就是 0 0 0 要变成 1 1 1 i i i次, 1 1 1 要变成 0 0 0 j 0 − j j_0-j j0j 次,相当于 D A G \tt DAG DAG 上算贡献。

这个过程不用建图,设 g [ t ] [ s ] [ i ] [ j ] g[t][s][i][j] g[t][s][i][j] 为操作了 t t t 次(是对于每个数位的操作),当前得到的数是 s s s,还有 i i i 1 1 1 要变成 0 0 0,还有 j j j 0 0 0 要变成 1 1 1,初始化就用上面的方法,转移暴力刷表就行了(就是下传标记嘛)

但是要注意枚举顺序,需要先枚举 i , j i,j i,j 再枚举 s s s,所以 s s s 应该放在最后一维才能再强调常数的这道题避免 T L E \tt TLE TLE,这一部分的时间复杂度 O ( n 3 2 n ) O(n^32^n) O(n32n)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 18;
const int N = 200;
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,a,b,p,i0,j0,pw[M],f[M][M][M],C[M][M],g[M][M][1<<17];
struct Matrix
{
	int n,m,a[N][N];
	Matrix() {n=m=0;memset(a,0,sizeof a);}
	void clear() {n=m=0;memset(a,0,sizeof a);}
	Matrix operator * (const Matrix &b) const
	{
		Matrix r;
		r.n=n;r.m=b.m;
		for(int i=0;i<n;i++)
			for(int j=0;j<m;j++)
				for(int k=0;k<b.m;k++)
					r.a[i][k]=(r.a[i][k]+1ll*a[i][j]*b.a[j][k])%MOD;
		return r;
	}
	void print()
	{
		puts("_________");
		for(int i=0;i<n;i++,puts(""))
			for(int j=0;j<m;j++)
				printf("%d ",(a[i][j]+MOD)%MOD);
	}
}A,F;
Matrix qkpow(Matrix a,int b)
{
	Matrix r;r.n=r.m=a.n;
	for(int i=0;i<a.n;i++) r.a[i][i]=1;
	while(b>0)
	{
		if(b&1) r=r*a;
		a=a*a;
		b>>=1; 
	}
	return r;
}
int npow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=1ll*r*a%MOD;
		a=1ll*a*a%MOD;
		b>>=1;
	}
	return r;
}
void init()
{
	int inv=MOD/2+1;pw[0]=1;
	for(int i=1;i<=n;i++)
		pw[i]=1ll*inv*pw[i-1]%MOD;
	for(int i=0;i<=n;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	}
}
int num(int x,int y)
{
	return x*j0+y;
}
void jb()
{
	m=1<<n;
	for(int s=0;s<m;s++)
	{
		int v=read(),j0=__builtin_popcount(s),i0=n-j0;
		for(int i=0;i<=i0;i++)
			for(int j=0;j<=j0;j++)
				g[i][j0-j][s]=1ll*f[i0][i][j]*v%MOD;//初始化嘛 
	}
	for(int x=0;x<n;x++)
	{
		for(int i=0;i<n-x;i++)
			for(int j=0;i+j<n-x;j++)//这里要卡 
				for(int s=0;s<m;s++)
				{
					if(s&(1<<x))//看转移方程就知道枚举顺序了 
						g[i][j][s]=(g[i][j][s]+g[i+1][j][s^(1<<x)])%MOD;
					if(!(s&(1<<x)))
						g[i][j][s]=(g[i][j][s]+g[i][j+1][s^(1<<x)])%MOD;
				}
	}
	for(int i=0;i<m;i++)
		printf("%d ",(g[0][0][i]+MOD)%MOD);
	puts("");
}
signed main()
{
	n=read();m=read();a=read();b=read();
	init();p=1ll*a*npow(b,MOD-2)%MOD;
	for(i0=0;i0<=n;i0++)
	{
		j0=n-i0+1;//由于编号的原因要设置成 开! 注意下 
		//一开始是在[(0,j0),0]
		A.clear();
		for(int i=0;i<=i0;i++)
			for(int j=0;j<j0;j++)
			{
				for(int x=0;x<=i;x++)
					for(int y=0;y<=j;y++)
					{
						int p1=num(i,j),p2=num(x,y);//不能直接赋值哟,因为(x,y)=(i,j)两边都能算 
						A.a[p1][p2]=(A.a[p1][p2]+1ll*C[i][x]%MOD*C[j][y]%MOD*(1-p)%MOD*pw[n-x-y])%MOD;
					}
				for(int x=i;x<=i0;x++)
					for(int y=j;y<j0;y++)
					{
						int p1=num(i,j),p2=num(x,y);
						A.a[p1][p2]=(A.a[p1][p2]+1ll*p*C[i0-i][x-i]*C[j0-j-1][y-j]%MOD*pw[x+y])%MOD;
					}
			}
		A.n=A.m=num(i0,j0);
		A=qkpow(A,m);
		for(int i=0;i<=i0;i++)
			for(int j=0;j<j0;j++)
				f[i0][i][j]=A.a[num(i,j)][num(0,j0-1)];
	}
	jb();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值