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;
}
posted @ 2025-04-01 08:35  zxh923  阅读(15)  评论(0)    收藏  举报