ABC216H - Random Robots——容斥、状压DP

H - Random Robots

题目描述

给定 k k k 个机器人在数轴上的初始位置,有 n n n 个时刻,每个时刻的每个机器人都有一半概率不动或向正方向走一格,每个时刻所有机器人的移动是同时进行的。

求所有机器人始终不重合的概率,输出取模 998244353 998244353 998244353 意义下的值。

数据范围
2 ≤ k ≤ 10 , 1 ≤ n ≤ 1000 , 0 ≤ x 1 ​ < x 2 < ⋯ < x k ≤ 1000 2≤k≤10,1≤n≤1000,0≤x_1​<x_2<⋯<x_k≤1000 2k10,1n1000,0x1<x2<<xk1000

题解

赛时看到这道题想了半个多小时,好难,完全不会做。后来看了题解,我四指痛苦地扭曲、中指像把利剑指向自己:这不是原题吗??还是我没看题解做出来过的原题!!

我们把机器人的移动放到 x − t x-t xt 图上,于是机器人的移动要么向右走一格,要么斜向右上走一格,要求变为“求所有机器人路径不相交的概率”。此概率就等于方案数除以 2 n k 2^{nk} 2nk,所以我们转变为求路径不相交的方案数。

由于始终不重合,我们不妨加上一句废话:“求所有机器人初始、最终位置不重合,中间路径不相交的方案数”。根据容斥原理,答案为路径至少0个交点的方案数-路径至少1个交点的方案数+路径至少2个交点的方案数…(由于规定初始和最终位置都不重,所以此处交点只算中间路径),所以问题又可以转变为“求中间路径至少偶数个交点的方案数-至少奇数个交点的方案数”。

我们只求没有交点,所以完全可以修改一下交点的定义:两路径完全交错算1个交点,“碰撞弹开”类型算2个交点
在这里插入图片描述
这样一来我们发现,两路径一旦有交点,那么偶数个交点和奇数个交点的方案数一定是相等的。在所有有交点的方案数中,偶数个交点和奇数个交点各占一半。所以对于所有方案,可以把“至少”去掉:“求偶数个交点的方案数-奇数个交点的方案数”,就可以求得交点数为0的方案数。

是不是感觉巨熟悉?没错!就是【NOI2021】路径交点

对于两条路径来说,交点个数奇偶性只与初始位置和最终位置是否交叉有关。这样根据 L G V \rm LGV LGV 引理,我们最终转化为“求结尾位置逆序对对数为偶的方案数-为奇的方案数”。

这题的范围显然是不允许用矩阵行列式的方式来求的,但是考虑到 k k k 很小,我们可以设计一个状压DP来做。

d p [ s ] [ j ] dp[s][j] dp[s][j] 表示只考虑集合 s s s 内的机器人,所有机器人结尾位置 ≤ j \le j j ± 1 ±1 ±1 系数的方案数,那么有如下转移

d p [ s ] [ j ] = d p [ s ] [ j − 1 ] + ∑ i ∈ s ( − 1 ) b i g g e r ( i , s ) d p [ s − { i } ] [ j − 1 ] ( n j − x i ) dp[s][j]=dp[s][j-1]+\sum_{i\in s}(-1)^{bigger(i,s)}dp[s-\{i\}][j-1]{n\choose j-x_i} dp[s][j]=dp[s][j1]+is(1)bigger(i,s)dp[s{i}][j1](jxin)

其中 b i g g e r ( i , s ) bigger(i,s) bigger(i,s) 表示集合 s s s 中大于 i i i 的数的个数,由于 i i i 是插在最后的,所以这个数就是增加的逆序对个数。

然后就做完了。时间复杂度 O ( 2 k n k ) O(2^knk) O(2knk)

代码

#include<cstdio>//JZM yyds!!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define uns unsigned
#define MAXN 1030
#define INF 1e18
#define lowbit(x) ((x)&(-(x)))
#define MOD 998244353ll
#define IF it->first
#define IS it->second
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
inline ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
ll fac[MAXN],inv[MAXN];
inline void init(int n){
	fac[1]=fac[0]=inv[1]=inv[0]=1;
	for(int i=2;i<=n;i++)fac[i]=fac[i-1]*i%MOD;
	inv[n]=ksm(fac[n],MOD-2,MOD);
	for(int i=n-1;i>1;i--)inv[i]=inv[i+1]*(i+1)%MOD;
}
inline ll C(int n,int m){
	if(m>n||m<0)return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int n,ms,m,k,x[15];
ll dp[MAXN][MAXN<<1];
signed main()
{
	k=read(),n=read(),ms=(1<<k);
	init(n);
	for(int i=1;i<=k;i++)x[i]=read()+1,m=max(m,x[i]+n);
	for(int i=0;i<=m;i++)dp[0][i]=1;
	for(int s=1;s<ms;s++){
		for(int j=1;j<=m;j++){
			dp[s][j]=dp[s][j-1];
			for(int i=k,nm=0;i>0;i--)
				if((s>>(i-1))&1){
					int t=s^(1<<(i-1)),cg=(nm&1)?MOD-1:1;
					dp[s][j]=(dp[s][j]+cg*dp[t][j-1]%MOD*C(n,j-x[i])%MOD)%MOD;
					nm++;
				}
		}
	}
	printf("%lld\n",dp[ms-1][m]*ksm((MOD+1)>>1,k*n,MOD)%MOD);
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值