#插头dp#洛谷 5074 HDU 1693 Eat the Trees

题目

给出 \(n*m\) 的方格,有些格子不能铺线,

其它格子必须铺,可以形成多个闭合回路。

问有多少种铺法? \(n,m\leq 12\)


分析

\(dp[n][m][S][0/1]\) 表示处理到 \((n,m)\)
目前插头的状态为 \(S\),并且左插头是否向右暴露,
插头的状态指的是轮廓线上的上插头是否向下暴露,这个分类讨论一下就可以了,
注意到右边界的位置左插头不能向右暴露,并且不能铺线的地方要继续更新方案


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
int n,m,al,a[12][12];
long long dp[4101][2],f[4101][2];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
signed main(){
	for (rr int T=iut();T;--T){
		memset(dp,0,sizeof(dp));
	    n=iut(),m=iut(),al=1<<m,dp[0][0]=1;
		for (rr int i=0;i<n;++i)
		for (rr int j=0;j<m;++j)
		    a[i][j]=iut();
		for (rr int i=0;i<n;++i)
		for (rr int j=0;j<m;++j){
			if (a[i][j]){
				for (rr int k=0;k<al;++k)
				if ((k>>j)&1){
					f[k][0]+=dp[k][0];//|
					f[k^(1<<j)][1]+=dp[k][0];//L
					f[k^(1<<j)][0]+=dp[k][1];//>
				}else{
					f[k][1]+=dp[k][1];//-
					f[k|(1<<j)][0]+=dp[k][1];//7
					f[k|(1<<j)][1]+=dp[k][0];//<
				}
			}else{
				for (rr int k=0;k<al;++k)
				if (!((k>>j)&1))
				    f[k][0]=dp[k][0];//不存在插头
			}
			if (j==m-1){
				for (rr int k=0;k<al;++k)
				    f[k][1]=0;//不能有暴露的左插头
			} 
			memcpy(dp,f,sizeof(dp));
			memset(f,0,sizeof(f));
		}
		printf("%lld\n",dp[0][0]);
	}
	return 0;
}

简化

可以发现,dp数组能够被压成一维,记录 \(m+1\) 个插头,

轮廓线往右移动一格左插头也向右移动一个。

注意位于行末时需要移除右侧插头并补上左侧插头,即调整轮廓线的状态


代码(HDU 1693)

#include <cstdio>
#include <cctype>
using namespace std;
int T,two[14],n,m,al;
long long f[8192],dp[8192];
int iut(){
    int ans=0; char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=ans*10+c-48,c=getchar();
    return ans;
}
int main(){
    T=iut(),two[0]=1;
    for (int i=1;i<=13;++i) two[i]=two[i-1]<<1;
    for (int _T=1;_T<=T;++_T){
        n=iut(),m=iut(),al=two[m+1]-1;
        for (int i=0;i<=al;++i) f[i]=0; f[0]=1;
        for (int i=0;i<=al;++i) dp[i]=f[i];
        for (int i=0;i<n;++i)
        for (int j=0;j<m;++j){
            int x=iut();
            for (int S=0;S<=al;++S) f[S]=0;
            for (int S=0;S<=al;++S)
            if (dp[S]){
                bool zuo=(S>>j)&1,upp=(S>>(j+1))&1;
                if (x){
                    f[S^two[j]^two[j+1]]+=dp[S];//转弯
                    if (zuo!=upp) f[S]+=dp[S];//直行
                }else if (!zuo&&!upp) f[S]+=dp[S];
            }
            for (int S=0;S<=al;++S)
            if (j<m-1) dp[S]=f[S];
                else dp[S]=(S&1)?0:f[S>>1];//行末最右侧的左插头不存在,插头整体往右移动一格,同时在左侧补上左插头
        }
        printf("Case %d: There are %lld ways to eat the trees.\n",_T,dp[0]);
    }
    return 0;
} 
posted @ 2021-08-25 10:51  lemondinosaur  阅读(26)  评论(0编辑  收藏  举报