机器人游戏
题解
听Cirno的蛊惑,来做了这道题,然后调了好久…
首先,我们根据样例解释,应该比较容易想到通过容斥来解决这个问题。
我们可以计算对于每个起点的组合,有多少个对于它合法的
X
,
Y
X,Y
X,Y组合。
显然,在有多个起点时,它会被几个不同的转移状况覆盖,那么我们就可以通过覆盖到它的转移状态。
总共的转移状态有四种,
0
0
0表示与之前相同,
1
1
1表示与之前不同,
2
2
2表示被赋值为
0
0
0,
3
3
3表示赋值为
1
1
1。
每种操作对于每个点的转移状态我们可以预处理出来。
显然,对于有至少一个起点的固定输入串,它的输出串是唯一的,毕竟每个字符的变化状态是固定的。
如果一个点作起点会爆掉,那这整个串就只能为空,特殊处理一下。
如果一个点同时被
0
0
0与
1
1
1或者
2
2
2与
3
3
3覆盖,那它就只有空格一种选择。
如果一个点同时被
0
0
0与
1
1
1中的一个和
2
2
2与
3
3
3中的一个覆盖,他就有
0
/
1
0/1
0/1中的某个数即空格两种选择。
否则,显然这个点是有
3
3
3种选择。
我们可以尝试对于一个起点组合,求出每个位置的倍覆盖情况。
暴力枚举起点集合,对于每个集合单独计算是
O
(
2
n
n
2
m
)
O\left(2^nn^2m\right)
O(2nn2m)的。
如果我们通过
S
S
S从
S
⊕
l
o
w
b
i
t
(
x
)
S\oplus lowbit(x)
S⊕lowbit(x)转移过来,可以做到
O
(
2
n
n
m
)
O\left(2^nnm\right)
O(2nnm),大概有
20
p
t
s
20pts
20pts。
考虑优化。
显然,对于
n
⩽
32
n\leqslant 32
n⩽32这个数据范围,我们很容易想到折半来优化。
分析一下,如果我们现在对于一个没有爆掉的串,它有用的起点个数。
如果它走的步数大于
n
2
\frac{n}{2}
2n,显然它有用的起点不会超过前面半,后面都会爆掉。
如果它走的步数小于
n
2
\frac{n}{2}
2n,真正对我们当前讨论点有用的起点也就只有这个带点前面不超过
n
2
\frac{n}{2}
2n个点,再前面就只有考虑有没有点被选即可,因为它们也只会让这个点拥有状态
0
0
0。
由于涉及到有的起点可能会爆炸的问题,使得会导致有的行只能为空,对每个位置产生的贡献造成影响。
我们可以考虑枚举最后一个被选择的起点在哪里。
如果这个点在前半段,之间像我们上面的做法一样,枚举状态即可。
如果这个点在后半段,可以考虑状压
d
p
dp
dp。
我们就可以考虑记
d
p
i
,
S
,
0
/
1
dp_{i,S,0/1}
dpi,S,0/1表示现在我们转移到了第
i
i
i个位置,前面不超过
n
2
\frac{n}{2}
2n个点的状态为
S
S
S,在前面有没有选择点作为起点。
每次预处理一下,对于一个节点,它前面的起点选择状态为
S
,
0
/
1
S,0/1
S,0/1时,它这一列所有纸条该位置的贡献乘积。
这样总共要预处理
n
n
n次,每次预处理时间复杂度
O
(
2
n
2
m
)
O\left(2^{\frac{n}{2}}m\right)
O(22nm),预处理后进行一次
d
p
dp
dp转移的复杂度为
2
n
2
n
2^{\frac{n}{2}}n
22nn。
这样的话可以做到
O
(
2
n
2
n
(
m
+
n
)
)
O\left(2^{\frac{n}{2}}n(m+n)\right)
O(22nn(m+n)),大概有
48
p
t
s
48pts
48pts。
我们看看还有没有可以优化的地方。
我们预处理的时候那个
m
m
m非常显眼,
m
m
m是
⩽
1000
\leqslant 1000
⩽1000的,就是它这里复杂度很大,后面
d
p
dp
dp的
n
n
n就很小。
其实我们这里只涉及被某几个状态覆盖的该位置的纸条计数的计算,每个状态的表示事实上都是
b
o
o
l
bool
bool类型,转移也就是
o
r
or
or或者
a
n
d
and
and。
这不是显然可以通过
bitset
\text{bitset}
bitset进行优化吗?恰好我们的计数
bitset
\text{bitset}
bitset也是可以轻松解决的。
我们记录
b
i
,
0
/
1
/
2
/
3
b_{i,0/1/2/3}
bi,0/1/2/3表示距离起点距离为
i
i
i,有哪些纸条被状态
0
/
1
/
2
/
3
0/1/2/3
0/1/2/3覆盖。
之后的转移与求解比较容易,直接操作即可。
恰好我们前面那个枚举的部分也是能用类似的方法优化的。
于是我们轻松的把这部分优化到了
O
(
2
n
2
m
ω
)
O\left(\frac{2^{\frac{n}{2}}m}{\omega}\right)
O(ω22nm)。
总时间复杂度
O
(
2
n
2
(
n
+
m
ω
)
n
)
O\left(2^{\frac{n}{2}}(n+\frac{m}{\omega})n\right)
O(22n(n+ωm)n)。
然而
O
n
e
I
n
D
a
r
k
\rm O\red{neInDark}
OneInDark会
O
(
2
n
2
n
+
n
m
)
O\left(2^{\frac{n}{2}}n+nm\right)
O(22nn+nm)的做法,快点膜它。
源码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define MAXN 1005
#define MAXM (1<<16)+5
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lowbit(x) (x&-x)
const int mo=1e9+7;
const int jzm=23;
const int zero=200000;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,m,len[MAXN],low[MAXM],hig[MAXM],bit[MAXM],pw2[MAXN],pw3[MAXN];
int dp[2][MAXM][2],g[MAXM][2],ans;char str[105];
bitset<MAXN>b[35][4],ot[35],tmp[MAXM][4],tp1,tp2,all;
int main(){
read(n);read(m);pw2[0]=pw3[0]=1;
for(int i=1;i<=m;i++)pw2[i]=2ll*pw2[i-1]%mo,pw3[i]=3ll*pw3[i-1]%mo;
for(int i=1;i<=m;i++){
scanf("%s",str+1);int p=0,now=0;
len[i]=(int)strlen(str+1);all[i]=1;
for(int j=1;j<=len[i];j++)
if(str[j]=='R')b[p][now][i]=1,p++,now=0;
else if(str[j]=='0')now=2;
else if(str[j]=='1')now=3;
else if(str[j]=='*')now^=1;
b[p][now][i]=1;len[i]=p;
for(int j=p+1;j<n;j++)b[j][0][i]=1;
for(int j=n-p;j<n;j++)ot[j][i]=1;
}
for(int S=1;S<(1<<16);S++){
for(int i=0;i<16;i++)if((S>>i)&1)hig[S]=i;
for(int i=0;i<16;i++)if((S>>i)&1){low[S]=i;break;}
bit[S]=bit[S>>1]+(S&1);
}
int up=n/2;
for(int i=1;i<(1<<up);i++)dp[0][i][0]=1;
for(int i=0;i<n;i++)
for(int S=1;S<(1<<up);S++){
int T=lowbit(S),x=hig[T],t1,t2,t3;
if(x<=i)for(int j=0;j<4;j++)
tmp[S][j]=tmp[S^T][j]|b[i-x][j];
else tmp[S][0]|=all;
tp1=(tmp[S][0]&tmp[S][1])|(tmp[S][2]&tmp[S][3])|ot[hig[S]];
tp2=(tmp[S][0]|tmp[S][1])&(tmp[S][2]|tmp[S][3]);
t1=tp1.count(),t2=tp2.count()-(tp1&tp2).count(),t3=m-t1-t2;
dp[0][S][0]=1ll*pw2[t2]*pw3[t3]%mo*dp[0][S][0]%mo;
}
for(int i=1;i<(1<<up);i++){
if(bit[i]&1)Add(ans,dp[0][i][0],mo);
else Add(ans,mo-dp[0][i][0],mo);
dp[0][i][0]=0;
}
for(int i=up;i<n;i++){
up=0;for(int j=1;j<=m;j++)if(i+len[j]<n)up=max(up,len[j]);
for(int S=1;S<(1<<up+1);S++){
int T=lowbit(S),x=low[T],t1,t2,t3;
for(int j=0;j<4;j++)tmp[S][j]=tmp[S^T][j]|b[x][j];
tp1=(tmp[S][0]&tmp[S][1])|(tmp[S][2]&tmp[S][3])|ot[i];
tp2=(tmp[S][0]|tmp[S][1])&(tmp[S][2]|tmp[S][3]);
t1=tp1.count(),t2=tp2.count()-(tp1&tp2).count(),t3=m-t1-t2;
g[S][0]=1ll*pw2[t2]*pw3[t3]%mo;
tp1=tmp[S][1]|(tmp[S][2]&tmp[S][3])|ot[i];
tp2=all&(tmp[S][2]|tmp[S][3]);
t1=tp1.count();t2=tp2.count()-(tp1&tp2).count(),t3=m-t1-t2;
g[S][1]=1ll*pw2[t2]*pw3[t3]%mo;
}
g[0][0]=g[0][1]=pw3[m-ot[i].count()];
int now=0,las=1,lim=up?(1<<up)-1:0;dp[now][0][0]=1;
for(int j=0;j<n;j++){
swap(now,las);
for(int S=0;S<=lim;S++)
for(int k=0;k<2;k++)if(dp[las][S][k]){
for(int t=0;t<2;t++){
if(j==i&&!t)continue;if(j>i&&t)continue;int T=S+S|t,tk=(T>>up)&1;
if(t)Add(dp[now][T&lim][k|tk],mo-1ll*g[T][k|(j<i)]*dp[las][S][k]%mo,mo);
else Add(dp[now][T&lim][k|tk],1ll*g[T][k|(j<i)]*dp[las][S][k]%mo,mo);
}
dp[las][S][k]=0;
}
}
for(int S=0;S<=lim;S++)for(int k=0;k<2;k++)
{if(S||k)Add(ans,mo-dp[now][S][k],mo);dp[now][S][k]=0;}
}
printf("%d\n",ans);
return 0;
}