[NOI2021]机器人游戏

机器人游戏

题解

听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) Slowbit(x)转移过来,可以做到 O ( 2 n n m ) O\left(2^nnm\right) O(2nnm),大概有 20 p t s 20pts 20pts

考虑优化。
显然,对于 n ⩽ 32 n\leqslant 32 n32这个数据范围,我们很容易想到折半来优化。
分析一下,如果我们现在对于一个没有爆掉的串,它有用的起点个数。
如果它走的步数大于 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;
}

谢谢!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值