[NOI2021] 机器人游戏

一、题目

点此看题

二、解法

全程在 \(\tt Cirno\_9\) 的帮助下补了这道题,果然他又帅又强👍

根据样例解释的提示,考虑对起点容斥。也就是枚举一个起点集合 \(S\),求有多少种输入使得从 \(S\) 中的起点出发,得到的输出相同,然后带上 \((-1)^{|S|+1}\) 的容斥系数即可。

起点对位置 \(i\) 的限制只有 \(4\) 种,即 赋值0/赋值1/不变/取反,可以直接分类讨论计算方案数:

  • 如果走出去了,那么整个序列的格子贡献都是 \(1\)
  • 如果同时拥有前两种或者后两种,那么 \(i\) 的贡献为 \(1\)
  • 如果前两种有一个,后两种有一个,那么 \(i\) 的贡献为 \(2\)
  • 如果只拥有四种中的一种,那么 \(i\) 的贡献为 \(3\)

暴力进行这个过程,对于每个机器人分开来计算,再用乘法原理即可得到答案,时间复杂度 \(O(n^2m2^n)\sim O(nm2^n)\)

考虑优化,主要矛盾在于 \(2^{n}\),我们想办法用折半的方法优化它,枚举最后的起点 \(p\),有这样的 \(\tt observation\)

  • \(p\leq \frac{n}{2}\),可以直接暴力枚举 \(S\),时间复杂度 \(O(nm2^{n/2})\)
  • \(p>\frac{n}{2}\),那么需要考虑的序列必然满足步数 \(s<n-p+1\)

对于第二种情况我们考虑 \(dp\),设 \(f(i,S,0/1)\) 表示现在考虑到了格子 \(i\),和格子 \(i\) 距离 \(n-p\) 以内的选取情况是 \(S\),在这个距离以外有无被选取的点(因为这会新增一个不变的限制),那么状态数是 \(O(n2^{n/2})\) 的。转移考虑枚举当前格子是否作为起点被选取,如果被选取会增加 \(-1\) 的容斥系数(即 \(dp\) 计算容斥系数的技巧)

时间复杂度 \(O(nm2^{n/2})\),现在的瓶颈在于 \(m\),考虑不同序列的 \(dp\) 转移方式是完全相同的,而且他们又独立所以支持乘法原理。所以考虑整体 \(dp\),转移时一次性考虑 \(m\) 个格子,我们需要现在需要做的是快速计算贡献。

开四个 bitset 维护每个序列的四种限制是否存在,那么只需要位运算就可以计算贡献。考虑如何处理出这些 bitset,在暴力枚举 \(S\) 时,我们在线处理 \(t_S\) 表示起点集合为 \(S\)bitset;在 \(dp\) 转移时,我们预处理 \(t_S\) 表示距离集合为 \(S\) 时的 bitset,然后就可以得到转移系数。

时间复杂度 \(O(\frac{nm2^{n/2}}{w})\)

#include <cstdio>
#include <bitset>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 1005;
const int N = 1<<16;
#define int long long
const int MOD = 1e9+7;
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,k,a[N],b[N],p2[M],p3[M];
int ans,f[N][2],g[N][2],dp[N],lg[N];
bitset<M> R,s1,s2,s3,in[M];
struct node
{
	bitset<M> b[4];
	node operator | (const node &v) const
	{
		node r;
		for(int i=0;i<4;i++) r.b[i]=b[i]|v.b[i];
		return r;
	}
	void set(int x,int y) {b[x].set(y);}
	int val()
	{
		s3=R&~((b[0]&b[1])|(b[2]&b[3]));
		s1=s3&(b[0]^b[1]);s2=s3&(b[2]^b[3]);
		int x=s1.count()+s2.count(),y=(s1&s2).count();
		return p2[y]*p3[x-2*y]%MOD;
	}
}h[M],t[N],Z;
void add(int &x,int y) {x=(x+y)%MOD;}
void work(int p)
{
	R=in[p];int L=1<<(n-p+1);
	memset(f,0,sizeof 0);f[0][0]=-1;
	for(int i=0;i<L;i++)
	{
		a[i]=t[i].val();
		node c9=t[i];c9.b[2].set();
		b[i]=c9.val();//sad
	}
	for(int j=1;j<=n;j++)
	{
		for(int i=0;i<L;i++) g[i][0]=g[i][1]=0;
		for(int i=0;i<L;i++)
		{
			int k=i<<1,w=k&(L-1),o=(k!=w),*z=(o?b:a);
			if(j!=p)//not choose
			{
				if(j<p) add(g[w][o],f[i][0]*b[w]);
				else add(g[w][o],f[i][0]*z[w]);
				add(g[w][1],f[i][1]*b[w]);
			}
			if(j<=p)//choose
			{
				w|=1;
				if(j<p) add(g[w][o],-f[i][0]*b[w]);
				else add(g[w][o],-f[i][0]*z[w]);
				add(g[w][1],-f[i][1]*b[w]);
			}
		}
		swap(f,g);
	}
	for(int i=0;i<L;i++)
		add(ans,f[i][0]+f[i][1]);
}
signed main()
{
	n=read();m=read();k=n/2;
	p2[0]=p3[0]=1;
	for(int i=1;i<=m;i++)
	{
		static char s[M]={};
		scanf("%s",s+1);Z.set(2,i);
		int w=2,len=0,t=strlen(s+1);
		p2[i]=p2[i-1]*2%MOD;
		p3[i]=p3[i-1]*3%MOD;
		for(int j=1;j<=t;j++)
		{
			if(s[j]=='R') h[len++].set(w,i),w=2;
			else if(s[j]=='*') w^=1;
			else w=s[j]-'0';
		}
		h[len].set(w,i);
		for(int j=1;j<=n;j++)
			if(len+j<=n) in[j].set(i);
		for(int j=len+1;j<=n;j++) h[j].set(2,i);
	}
	dp[0]=lg[0]=-1;
	for(int i=1;i<N;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<(1<<k);i++) dp[i]=-dp[i&(i-1)];
	//p<=n/2
	for(int i=1;i<=n;i++)
	{
		for(int s=1;s<(1<<k);s++)
		{
			int d=i-lg[s&(-s)]-1;
			t[s]=t[s&(s-1)]|(d<0?Z:h[d]);
			R=in[lg[s]+1];
			dp[s]=dp[s]*t[s].val()%MOD;
		}
	}
	for(int s=1;s<(1<<k);s++) add(ans,dp[s]);
	//p>n/2
	for(int s=1;s<N;s++)
		t[s]=t[s&(s-1)]|h[lg[s&(-s)]];
	for(int i=k+1;i<=n;i++) work(i);
	printf("%lld\n",(ans+MOD)%MOD);
}
posted @ 2022-07-27 10:09  C202044zxy  阅读(258)  评论(0编辑  收藏  举报