AtCoder Grand Contest 017F Zigzag 状压dp

36 篇文章 0 订阅
21 篇文章 0 订阅

Description


有一个边长为n的正三角形,定义一条路径为从(1,1)出发,每次向左下或向右下走一格走n-1次到底
问有多少种方案使得可以选出m条不相同的路径,对于任意的i,保证第i条不在第i-1条的左侧
同时给出m个限制形如(x,y,z),表示第x条路径的第y步一定要往z这个方向走
n , m ≤ 20 n,m\le20 n,m20

Solution


注意到起点是一样的,那么我们可以用一个n-1位二进制数表示一条路径
问题变成求m个n-1位二进制数a[],对于第i个数a[i],保证任意前j位1的数量不小于a[i-1]的前j位1的数量,且某些位置确定了的方案
一个响法就是f[i,S]表示第i个数选了S,那么枚举下一个数转移就是 O ( n 4 n ) O(n4^n) O(n4n)的,然后我就不会做了

回想插头dp的做法,我们可以一位位转移。设f[i,j,S]表示选了i个数字,第i个数的前j位和S的前j位一样,且后面不会走到S的左边的方案数
讨论一下S第j+1位上的数字
若S上为1,限制为0,那么我们填不了
若S上为1,限制为1,那么我们直接填
若S上为0,限制为0,那么我们就可以填0,转移到f[i,j+1,S]
若S上为0,限制为1,那么我们可以在j+1填1,然后找到j+1向后第一个非零位k,把k变成0。这样是为了满足不会走到左侧的限制
若没有限制,就分别当作限制0和限制1填就行辣

一开始wa了很多发是因为没发现长度为n的路径只需要走n-1步。。。

Code


#include <stdio.h>
#include <string.h>
#define rep(i,st,ed) for (register int i=st;i<=ed;++i)
#define fill(x,t) memset(x,t,sizeof(x))

const int MOD=1e9+7;
const int N=200005;

int f[2][21][624288];
int lm[21][21],n;

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void upd(int &x,int v) {
	x+=v; (x>=MOD)?(x-=MOD):0;
}

int ch(int S,int x) {
	if ((S>>x)&1) return S;
	int w=((1<<n)-(1<<x))&S;
	if (w) S-=(w&-w);
	return S+(1<<x);
}

int main(void) {
	freopen("data.in","r",stdin);
	n=read()-1; int m=read(),k=read();
	rep(i,1,k) {
		int x=read(),y=read();
		lm[x][y]=read()+1;
	}
	f[1][0][0]=1;
	rep(i,1,m) {
		int now=i&1,nxt=!now;
		rep(j,0,n-1) {
			for (int S=0;S<(1<<n);++S) {
				if (!f[now][j][S]) continue;
				if (lm[i][j+1]==0) {
					if (!((S>>j)&1)) upd(f[now][j+1][S],f[now][j][S]);
					upd(f[now][j+1][ch(S,j)],f[now][j][S]);
				} else if (lm[i][j+1]==1) {
					if (!((S>>j)&1)) upd(f[now][j+1][S],f[now][j][S]);
				} else upd(f[now][j+1][ch(S,j)],f[now][j][S]);
			}
		}
		fill(f[nxt],0);
		for (int S=0;S<(1<<n);++S) upd(f[nxt][0][S],f[now][n][S]);
	}
	int ans=0;
	for (int S=0;S<(1<<n);++S) upd(ans,f[m&1][n][S]);
	printf("%d\n", ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值