AtCoder agc017_f Zigzag
首先我们考虑一个显然的 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;
}