一、题目
有 \(n\) 个机器人排成一排,有 \(m\) 个时刻,每个时刻每个机器人有 \(1/2\) 的概率向右走一步,有 \(1/2\) 的概率在原地不懂。初始第 \(i\) 个机器人在位置 \(x_i\),问所有机器人不相撞的概率,答案模 \(998244353\)
\(n\leq 10,x_i,m\leq 1000\)
二、解法
首先转成不相撞的方案数,最后再除以 \(2^{nm}\)
要求不相撞的方案数可以考虑一个逆序对容斥,我们枚举最终位置序列 \(y_i\):
\[\sum_{y}(-1)^{\pi(y)}\prod_{i=1}^n{m\choose y_i-x_i} \]解释一下这个容斥,如果两个机器人重合那么我们就交换它们的操作序列,这个交换操作会使得逆序对减少 \(1\),所以一种方案是会在逆序对更少的计算中被统计,考虑逆序对数量为 \(n\) 的方案被统计的次数:
\[\sum_{i=0}^n(-1)^i{n\choose i}=[n=0] \]那么最后只会把没有逆序对的方案算进答案里,也就是所有机器人不相撞的方案数。
现在考虑怎么计算这个容斥式子,主要问题在于确定序列 \(y\),由于数据范围很小可以考虑 \(dp\),具体来说我们最外层从小到大枚举最终位置 \(y\),然后考虑向状态 \(s\) 中加入最终位置为 \(y\) 的某个点,已经在集合中的最终位置都会比当前枚举的最终位置小,所以这时候很方便统计逆序对数,时间复杂度 \(O(n2^nm)\)
三、总结
路径不相交问题首选逆序对容斥,然后分为两种情况:如果终点确定那么可以套用 \(\tt 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\n",(ans+MOD)%MOD);
}