[ABC216H] Random Robots

一、题目

点此看题

(n) 个机器人排成一排,有 (m) 个时刻,每个时刻每个机器人有 (1/2) 的概率向右走一步,有 (1/2) 的概率在原地不动。初始第 (i) 个机器人在位置 (x_i),问所有机器人不相撞的概率,答案模 (998244353)

(nleq 10,x_i,mleq 1000)

二、解法

首先转成不相撞的方案数,最后再除以 (2^{nm})

要求不相撞的方案数可以考虑一个逆序对容斥,我们枚举最终位置序列 (y_i)

[sum_{y}(-1)^{pi(y)}prod_{i=1}^n{mchoose y_i-x_i} ]

解释一下这个容斥,如果两个机器人重合那么我们就交换它们的操作序列,这个交换操作会使得逆序对减少 (1),其实这和 ( t LGV) 引理差不多。那么最后只会把没有逆序对的方案算进答案里,也就是所有机器人不相撞的方案数。

现在考虑怎么计算这个容斥式子,主要问题在于确定序列 (y),由于数据范围很小可以考虑 (dp),具体来说我们最外层从小到大枚举最终位置 (y),然后考虑向状态 (s) 中加入最终位置为 (y) 的某个点,已经在集合中的最终位置都会比当前枚举的最终位置小,所以这时候很方便统计逆序对数,时间复杂度 (O(n2^nm))

三、总结

路径不相交问题首选逆序对容斥,然后分为两种情况:如果终点确定那么可以套用 ( t LGV) 引理;如果数据范围小那么可以 (dp) 计算容斥系数。

状压 (dp) 也可用于确定序列问题上,只是这是时候需要多消耗 (O()值域()) 的复杂度。

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 2005;
const int MOD = 998244353;
#define int long long
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[M],f[M],p[1<<11],dp[1<<11];
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;
}
signed main()
{
	n=read();m=read();
	for(int i=0;i<n;i++)
		a[i]=read();
	f[0]=dp[0]=1;
	for(int i=1;i<=m;i++)
		f[i]=f[i-1]*(m-i+1)%MOD*qkpow(i,MOD-2)%MOD;
	for(int i=0;i<(1<<n);i++)
		p[i]=__builtin_parity(i)?-1:1;
	for(int i=0;i<=a[n-1]+m;i++)
		for(int s=(1<<n)-1;s>=0;s--)
			for(int j=0;j<n;j++) if(!(s&(1<<j)))
				if(a[j]<=i && i<=a[j]+m)
				{
					dp[s|(1<<j)]=(dp[s|(1<<j)]
					+p[s>>j]*dp[s]*f[i-a[j]])%MOD;
				}
	int ans=dp[(1<<n)-1]*qkpow((MOD+1)/2,n*m)%MOD;
	printf("%lld
",(ans+MOD)%MOD);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15205953.html