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,m≤20
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;
}