[agc017F]Zigzag

9 篇文章 0 订阅

题目大意

有一个n行的三角形,第i行有i个格子。第i行第j个格子用(i,j)表示。从(i,j)可以到达(i+1,j)和(i+1,j+1)。现在要确定m条从(1,1)出发到第n行的路径。设第a条路径走到的第b个格子是(b,X[a,b]),对于任意a < b,不能存在i,使得X[a,i]>X[b,i]。同时还有K条形如(a,b,c)的限制,表示第a条路径第b个点到第b+1个点必须往方向c走。
求合法的方案数模 109+7

n,m≤20

分析

如果尝试直接表示某一行m条路径的状态,运行效率是很低的。
考虑到每一个点出发只有两个方向,那么可以用二进制数来表示一条路径(0表示往(i+1,j)走,1表示(i+1,j+1))。当确定第i条路径的时候,只要知道第i-1条路径就可以了。
假设正在确定第i条路径,并且路径前j-1位已经确定了,同时前j-1位还和第i-1条路径相同。设S表示第i-1条路径的状态。现在如果S的第j位为0,且第i条路径的第j位为1,那么第j位出现开始不同。接下来我们找到S在第j位之后的第一个1,把这个1变成0,并把第j位赋为1,即强行把第i-1条路径改成前j位与第i条一致,容易证明这是等价的。因为在这两位之间无论第i条路径怎么走都不会不合法。
如果找不到下一个1,那么第i条路径之后就可以随便走了。
根据上面的思路就可以DP了。时间复杂度 O(nm2n1)

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=20,M=524288,mo=1e9+7;

typedef long long LL;

int n,m,K,lim[N][N],g[M],f[2][M],p,q,nxt[N][M],h[N][M],ans,T;

int main()
{
    scanf("%d%d%d",&n,&m,&K); T=1<<n-1;
    memset(lim,255,sizeof(lim));
    for (int a,b;K--;scanf("%d",&lim[a-1][b-1])) scanf("%d%d",&a,&b);
    for (int i=1,j,k,la;i<M;i++)
    {
        for (j=T>>1,k=n-2,la=0;k>=0;k--,j>>=1)
        {
            nxt[k][i]=la;
            if (j&i) la=j;
        }
    }
    g[0]=1;
    for (int i=0,j,k,st;i<m;i++)
    {
        p=0; q=1;
        memcpy(f[0],g,sizeof(g));
        memset(h,0,sizeof(h));
        for (j=0,k=1;j<n-1;j++,p^=1,q^=1,k<<=1)
        {
            memset(f[q],0,sizeof(f[q]));
            for (st=0;st<T;st++) if (f[p][st]>0)
            {
                if (lim[i][j]==-1)
                {
                    f[q][st]=(f[q][st]+f[p][st])%mo;
                    if (!(k&st))
                    {
                        if (!nxt[j][st]) h[j+1][st|k]=(h[j+1][st|k]+f[p][st])%mo;
                        else f[q][(st|k)^nxt[j][st]]=(f[q][(st|k)^nxt[j][st]]+f[p][st])%mo;
                    }
                }else if (lim[i][j]==0)
                {
                    if ((k&st)) continue;
                    f[q][st]=(f[q][st]+f[p][st])%mo;
                }else
                {
                    if ((k&st))
                    {
                        f[q][st]=(f[q][st]+f[p][st])%mo;
                    }else
                    {
                        if (!nxt[j][st]) h[j+1][st|k]=(h[j+1][st|k]+f[p][st])%mo;
                        else f[q][(st|k)^nxt[j][st]]=(f[q][(st|k)^nxt[j][st]]+f[p][st])%mo;
                    }
                }
            }
        }
        for (j=2,k=2;j<n;j++,k<<=1)
        {
            for (st=0;st<k;st++) if (h[j-1][st]>0)
            {
                if (lim[i][j-1]!=1) h[j][st]=(h[j][st]+h[j-1][st])%mo;
                if (lim[i][j-1]!=0) h[j][st|k]=(h[j][st|k]+h[j-1][st])%mo;
            }
        }
        for (st=0;st<T;st++) g[st]=(f[p][st]+h[n-1][st])%mo;
    }
    ans=0;
    for (int st=0;st<T;st++) ans=(ans+g[st])%mo;
    printf("%d\n",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值