题目传送门
首先我们考虑一个显然的 dp,\(f_{i,s}\) 表示前 \(i\) 条线,当前是从左到右的第 \(i\) 条线,走法状压后为 \(s\) 的方案数。
转移直接枚举上一条线怎么走的,时间复杂度 \(4^n\times poly(nm)\),显然狠狠超时。
发现我们的状态其实并没有很爆,但是转移太慢了,考虑怎么加速转移。
可以使用轮廓线 dp。同时加上一维 \(j\) 表示当前要走第 \(j\) 步。这时我们 \(s\) 的定义也有变化,现在 \(s\) 应该被分成两段,前半段为当前这条线怎么走的,后半段为当前这条线后半段不能越过哪条线,两段的分界线就是 \(j\)。
这里注意到,根据状态的合法性,当前线的前半段是不会越过上一条线的前半段的。且根据后文我们的操作,当前线和要求线在当前层始终位于同一个点。
转移则考虑当前向哪边走:
-
向左走。此时有两个要求,一个是这一步不能被要求向右,且下一步要求线必须向左。如果能转移,注意到 \(s\) 不会变,直接转移即可。
-
向右走。此时只有一个要求,这一步不能被要求向左。而这里我们要对要求线这一步向哪里再进行分类讨论:
-
要求线向右:那么 \(s\) 不变,直接转移即可。
-
要求线向左:这里可以画几个图,发现要求线会变成这样:当前步由向左变为向右,然后找到要求线下一个向右的位置,这一步变为向左(如果找不到则不做这个操作),然后直接转移即可。
-
AC code:
#include<bits/stdc++.h>
#define int long long
#define N 25
#define M 1050005
#define pii pair<int,int>
#define x first
#define y second
#define mod 1000000007
#define inf 2e18
using namespace std;
int T=1,n,m,k,f[2][N][M],a[N][N];
bool check(int x,int s){for(int i=1;i<n;i++){if(a[x][i]!=-1){if((s>>(i-1)&1)!=a[x][i])return 0;}}return 1;
}
int lowbit(int x){return x&-x;
}
void solve(int cs){cin>>n>>m>>k;memset(a,-1,sizeof a);for(int i=1;i<=k;i++){int x,y;cin>>x>>y;cin>>a[x][y];}int lim=1<<n-1;for(int i=0;i<lim;i++){f[1][n][i]=check(1,i);}for(int i=2;i<=m;i++){for(int j=2;j<=n;j++){for(int s=0;s<lim;s++){f[i&1][j][s]=0;}}for(int j=1;j<n;j++){for(int s=0;s<lim;s++){int las=f[i&1][j][s];if(j==1)las=f[i-1&1][n][s];if(a[i][j]!=1&&!(s>>(j-1)&1)){(f[i&1][j+1][s]+=las)%=mod;}if(a[i][j]!=0){if(s>>(j-1)&1)(f[i&1][j+1][s]+=las)%=mod;else{int p=lowbit(s>>j)<<j;(f[i&1][j+1][(s|(1<<j-1))-p]+=las)%=mod;}}}}}int res=0;for(int s=0;s<lim;s++){(res+=f[m&1][n][s])%=mod; }cout<<res<<'\n';
}
signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// cin>>T;
// init();for(int cs=1;cs<=T;cs++){solve(cs);}return 0;
}