题目大意
有一个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(nm2n−1)
#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;
}