[agc017f]Zigzag

前言

DP题,我当然不会啦。

题目大意

请你找到m个n位二进制数,对于相邻两个,i+1的前j位的和不小于i的前j位的和。
此外还有对于某个二进制数某位必须是几的若干个限制。

DP

很容易想到设dp[i,s]表示做到第i个二进制数第i个是s,每次枚举前一个,复杂度很大。
转移复杂度太大了,我们来尝试优化。
如果可以不用枚举前一个就好了。
那么不如让前一个对着当前的改变吧!
设dp[i,j,s]表示做到第i个二进制数,已经确定了它的前j位和s的前j位一致,其中你只要保证了这个新的二进制数任意前缀和和s满足,就能满足限制。
这个s就是前一个变化而来的。
变化规则也很简单。
如果你确定当前位为1,s这位也是1,当然没问题。
0与0同理。
而0与1非法。
然后1与0,则为了满足s的前j位与当前二进制数前j位一致,要对s做改变。
你可以找到在这之后第一个有1的位置,让s这一位-1,第j位+1。
当然之后可能已经没有1了,这说明当前二进制数可以为所欲为,然后我们直接把第j位+1即可。
这样dp就简单了。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=20+10,mo=1000000007;
int dp[2][maxn][(1<<21)+10],nxt[(1<<21)+10][maxn];
int bz[maxn][maxn];
int i,j,k,l,r,s,t,n,m,p,ans,now;
int main(){
    scanf("%d%d%d",&n,&m,&p);
    n--;
    fo(i,0,(1<<n)-1){
        t=-1;
        fd(j,n-1,0){
            if ((i&(1<<j))) t=j;
            nxt[i][j]=t;
        }
    }
    fo(i,1,m)
        fo(j,0,n-1)
            bz[i][j]=-1;
    fo(i,1,p){
        scanf("%d%d%d",&j,&k,&l);
        bz[j][k-1]=l;
    }
    dp[0][n][0]=1;
    now=1;
    fo(i,1,m){
        fo(s,0,(1<<n)-1) dp[now][0][s]=dp[1-now][n][s];
        fo(j,0,n)
            fo(s,0,(1<<n)-1)
                dp[1-now][j][s]=0;
        fo(j,0,n-1)
            fo(s,0,(1<<n)-1)
                if (dp[now][j][s]){
                    fo(k,0,1){
                        if (bz[i][j]!=-1&&bz[i][j]!=k) continue;
                        t=(s&(1<<j));
                        if (t) t=1;
                        if (k==0&&t==1) continue;
                        if (k==t) (dp[now][j+1][s]+=dp[now][j][s])%=mo;
                        else{
                            r=nxt[s][j];
                            if (r==-1) (dp[now][j+1][s+(1<<j)]+=dp[now][j][s])%=mo;
                            else (dp[now][j+1][s+(1<<j)-(1<<r)]+=dp[now][j][s])%=mo;
                        }
                    }
                }
        now=1-now;
    }
    fo(s,0,(1<<n)-1) (ans+=dp[1-now][n][s])%=mo;
    (ans+=mo)%=mo;
    printf("%d\n",ans);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值