[AtCoder Grand Contest 017] F: Zigzag (agc017F)

原题链接
https://agc017.contest.atcoder.jp/tasks/agc017_f

Description

给出一个N行的三角形,第i行有i个点

在(X,Y)这个点,可以走向(X+1,Y),也可以走向(X+1,Y+1)

现在需要找出M条路径,这M条路径不能交叉,即第i条路径的所到的每一个点的都必须在第i-1条路径同一行到达的点的右边(可以相同)

同时给出K条限制,形如Ai,Bi,Ci,表示第Ai条路径在第Bi行向下必须+1或者必须不+1

两条路径可以是完全相同的
求有多少种合法的选法选出M条路径
N,M<=20
K<=(N-1)M

Solution

我们发现一条路径可以用一个二进制数表示
要求就是第i个数每一位的前缀1数量必须大于等于第i-1个数的

考虑状压DP

Fi,S,T F i , S , T 表示做到第i条路径,第i-1条路径的数为S ,第i条路径为T
直接转移一下,用个求和什么优化一下
大概是 O(m22n) O ( m 2 2 n )

明显会超时
换一种思路
我们只记录第i-1条路径的状态,然后按位转移

Fi,j,S F i , j , S 表示当前做到第i条路径的第j位

我们发现如果S全部记录的是第i-1条路径的状态,那么在转移到i+1的时候我们无法得知第i条路径的状态,GG

思考我们转移到底需要什么
我们需要第i-1条和第i条前j位用了多少个1,后面就是上一条的状态

然后我们发现,如果第i-1条路径这一位是1,并且前面的状态都完全相同,那么代表这一位也必须选1

以下就真的是人类智慧了

S的前j位表示第i条路径的状态,后面n-j位表示第i-1条路径的状态,即第i条路径能走的最靠左的走法
然后S的前j位的1的个数与第i-1条路径的前j位的1的个数相同(但状态不一定相同)

现在我们只需要考虑转移

找到第j位以后第一个S为0的位(因为如果为1那么只能选1没有贡献)p

现在这一位选0和选1分开考虑,如果选0,那么S不变

如果选1,那么将S的第p位改成1,将第p位以后第一个1的位改为0,j转移到p,后面的都不变

举个例子, Fi,2,101011 F i , 2 , 101011 转移到 Fi,4,101011 F i , 4 , 101011 Fi,4,101101 F i , 4 , 101101

转移到最后了就统计一下加到i+1去

然后一个状态的第i为的后面第一个0/1的位置可以 O(n2n) O ( n 2 n ) 预处理
然后发现第一维i空间上并没有用,直接去掉

以上我们都还没有考虑限制的问题
既然要保证满足限制,我们在第p为分类选0/1的时候要特判

还有一种情况,从j转移到p的时候我们知道第j+1位到第p-1位都是1,那么如果有一个限制是这一条路径在这之间必须选一个0那么就GG了,这种情况可以用一个循环判断(实际上不太影响复杂度),可能也是我跑的慢的原因

最后复杂度是 O(n22n) O ( n 2 2 n ) 解决问题

Code

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define LL long long
#define mo 1000000007
#define M 1048580
#define N 21
using namespace std;
int f[N][M];
int n,m,l,a[N][N],cf[N],qs[M][N][2];
int main()
{
    cin>>n>>m>>l;
    memset(a,255,sizeof(a));
    cf[0]=1;
    fo(i,1,n) cf[i]=cf[i-1]*2;
    fo(i,1,l)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        a[x][y]=z;
    }
    n--;
    fo(c,0,cf[n]-1)
    {
        int w1=0,w2=0;
        fod(i,n,0)
        {
            qs[c][i][0]=w1,qs[c][i][1]=w2;
            if(i>0)
            {
                if(c&cf[i-1]) w2=i;
                else w1=i; 
            }
        }
    }
    f[0][0]=1;
    LL ans;
    fo(i,1,m)
    {
        fo(j,0,n-1)
        {
            fo(c,0,cf[n]-1)
            {
                if(!f[j][c]) continue;
                int p=qs[c][j][0];
                if(p&&p<=n)
                {
                    fo(k,j+1,p-1) 
                    {
                        if(a[i][k]==0) 
                        {
                            f[j][c]=0;
                            break;
                        } 
                    }
                    if(a[i][p]!=1) 
                    {
                        (f[p][c]+=f[j][c]);
                        if(f[p][c]>=mo) f[p][c]-=mo;
                    }
                    if(a[i][p]!=0) 
                    {
                        int q=qs[c][p][1];
                        if(q&&q<=n) 
                        {
                            int v=(c|cf[p-1])-cf[q-1];
                            (f[p][v]+=f[j][c]);
                            if(f[p][v]>=mo) f[p][v]-=mo;
                        }
                        else 
                        {
                            int v=c|cf[p-1];
                            (f[p][v]+=f[j][c]);
                            if(f[p][v]>=mo) f[p][v]-=mo;
                        }
                    }
                }
                else 
                {
                    fo(k,j+1,n) 
                    {
                        if(a[i][k]==0) 
                        {
                            f[j][c]=0;
                            break;
                        } 
                    }
                    (f[n][c]+=f[j][c]);
                    if(f[n][c]>=mo) f[n][c]-=mo;
                }
                f[j][c]=0;
            }
        }
        if(i==m)
        {
            ans=0;
            fo(c,0,cf[n]-1) (ans+=f[n][c])%=mo;
        }
        else
        fo(c,0,cf[n]-1)
        {
            f[0][c]=f[n][c];
            f[n][c]=0;
        }
    }
    printf("%lld\n",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值