[agc017f]Zigzag

题目大意

给你一个有n*(n-1)/2个点的等边三角形,顶端的点为(1,1),对于第i行,一共有i个点(i,1..i),你要在这个三角形上画m条折线,每条线包含n个点。具体的,假如你目前折线端点画到(i,j),你可以选择画到(i+1,j)或(i+1,j+1)。
现在给你约束:
1,对于任意折线a,b,设X[a][i]表示折线a的第i个端点为(i,X[a][i]),若a小于b,则对于所有i=1~n,都有X[a][i]≤X[b][i]。
2,给出若干组(A,B,C),表示折线A在B-1到B的过程中只能走0(左边)或者1(右边)

解题思路

很容易搞出一个暴力DP,设f[i][s]表示处理了前i条,第i条的曲折装态为s,s某一位是0则代表向左走,1类似。
那么暴力转移是O(n4^n)的。
考虑优化。我们知道s能够转移到s’,必须保证s的每一个前缀和都小于s’,从这里入手。
设f[i][j][s]表示做到第i条的第j行,这一条的前j行和s的状态一样,并且在将来这一条折线不会在s所代表折线的左边。
转移很简单,枚举第j+1行走哪边。假如走左边,那么s的这一位必须是0;走右边,s的这一位如果是1直接走,如果不是就从后面找一个1把他移动到这里(没有就直接把这位改成1),转移即可。

代码

#include<cstdio> 
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
const int N=1e6+5,M=2e6+5,mo=1e9+7;
int f[2][(1<<20)+5],c[25][25],n,m,K,x,y,z,q1,q2,i,j,s,ts,ns,ds,ans;
int main()
{
    freopen("t4.in","r",stdin);
    //freopen("t4.out","w",stdout);
    scanf("%d %d %d",&n,&m,&K);
    n--;
    fo(i,1,K)
    {
        scanf("%d %d %d",&x,&y,&z);
        c[x][y-1]=z+1;
    }
    f[0][0]=1;
    q1=0;
    q2=1;
    ts=1<<n;ts--;
    fo(i,1,m)
        fo(j,0,n-1)
        {
            fo(s,0,ts)
            if (f[q1][s])
            {
                //left
                if (c[i][j]!=2&&((s>>j)&1)==0)
                {
                    f[q2][s]+=f[q1][s];
                    if (f[q2][s]>=mo) f[q2][s]-=mo;
                }
                //right
                if (c[i][j]!=1)
                {
                    if ((s>>j)&1) ns=s;
                    else
                    {
                        ns=((-1)^(1<<j+1)-1)&s;
                        if (!ns) ds=0;
                        else ds=ns&(-ns);
                        ns=s^(1<<j)^ds;
                    }
                    f[q2][ns]+=f[q1][s];
                    if (f[q2][ns]>=mo) f[q2][ns]-=mo;
                }
            }
            fo(s,0,ts) f[q1][s]=0;
            q1^=1;
            q2^=1;
        }
    fo(s,0,ts) 
    {
        ans+=f[q1][s];
        if (ans>=mo) ans-=mo;
    }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值