#10172. 「一本通 5.4 练习 1」涂抹果酱 【 三进制状态压缩 】【 方案数 】

Tyvj 两周年庆典要到了,Sam 想为 Tyvj 做一个大蛋糕。蛋糕俯视图是一个 N×M 的矩形,它被划分成 N×M 个边长为 1×1 的小正方形区域(可以把蛋糕当成 NNN 行 MMM 列的矩阵)。蛋糕很快做好了,但光秃秃的蛋糕肯定不好看!所以,Sam 要在蛋糕的上表面涂抹果酱。果酱有三种,分别是红果酱、绿果酱、蓝果酱,三种果酱的编号分别为 1,2,31,2,31,2,3。为了保证蛋糕的视觉效果,Admin 下达了死命令:相邻的区域严禁使用同种果酱。但 Sam 在接到这条命令之前,已经涂好了蛋糕第 KKK 行的果酱,且无法修改。
现在 Sam 想知道:能令 Admin 满意的涂果酱方案有多少种。请输出方案数 mod106。若不存在满足条件的方案,请输出 000。

样例输入(点击右上角复制按钮 即可复制)

2 2 
1 
2 3

样例输出

3

 

因为数据范围中M很小,可以想到是状态压缩,而且毫无疑问地会选择压缩每一行。

因为这道题中有3种需要进行区分的果酱,所以使用三进制压缩,分别用0、1、2来表示。

然后按照普通的状压DP枚举每一行,进行合法方案数上的转移即可。

并且可以考虑预处理来优化

1. 每一行总共有 3^{m}-1种状态,但是因为相同颜色的果酱不能放在相邻的位置,所以每一行的不可行状态有很多。

    e.g: m=3时,枚举了状态 111 或 000 或 222,显然,这样是没有必要的。

    这就需要预处理出一行中的可行状态。经过统计,可以发现,即便是m=5,一行中的合法状态也只有48种,姑且当作50吧。

2. 在DP时,并不需要将每一种状态对应的十进制记录出来,更省空间且更简便的做法是:在储存时编号,这样就可以在遍历上一行和当前行的状态时直接取编号  ->这也是状压DP的常见套路了吧

3. 预处理行与行之间的判断

    使得时间复杂度有很高的保证。

总体最高时间复杂度为n\times 50\times 50+50\times 50\div 2+250\times 5 (也有粗略的四舍五入啊)大概250万的样子

 

细节问题也要注意啊:在运算过程中也有溢出的可能性

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define N 10006
#define mod 1000000
using namespace std;

inline int wread(){
	char c=getchar ();int flag=1,wans=0;
	while (c<'0'||c>'9'){if (c=='-') flag=-1;c=getchar ();}
	while (c>='0'&&c<='9'){wans=wans*10+c-'0';c=getchar ();}
	return wans*=flag;
}

int n,m;

int thre[250];//3^i的值 
int lin_cal[5];//临时存储 
int cal[50][5],top_cal;//每一种合法状态的每一位的数字  合法状态用编号储存 
int pre[50];//取出合法的状态 
int bel[250];
//三进制处理
void getthree(int x){
	thre[0]=1;
	for (int i=1;i<=x;++i)
		thre[i]=thre[i-1]*3;
	for (int i=0;i<thre[x];++i){//不能取等 
		int nx=i,top_wei=0;
		while (nx)	{
			lin_cal[++top_wei]=nx%3;
			nx/=3;
		}
		bool Jud=true;
		for (int j=2;j<=x;++j)
			if (lin_cal[j]==lin_cal[j-1])	{Jud=false;break;}
		if (!Jud)	continue;
		++top_cal;
		pre[top_cal]=i;
		bel[i]=top_cal;
		for (int j=1;j<=x;++j){
			cal[top_cal][j]=lin_cal[j];
		}
	}
}

bool jud[50][50];
//预处理 判断
void getjud (){
	for (int i=1;i<=top_cal;++i){
		jud[i][i]=false;
		for (int j=i+1;j<=top_cal;++j){
			bool Jud=true;
			for (int k=1;k<=m;++k){
				if (cal[i][k]==cal[j][k])	{Jud=false;break;}
			}
			if (!Jud)	jud[i][j]=jud[j][i]=false;
			else jud[i][j]=jud[j][i]=true;
		}
	}
}

int made_l,l_zh;
int dp[N][50];

int main (){
	n=wread();m=wread();
	
	getthree(m);
	getjud();
	
	made_l=wread();
	for (int i=1;i<=m;++i){
		lin_cal[i]=wread();
		lin_cal[i]--;
		l_zh+=lin_cal[i]*thre[m-i];
	}
	
	
	if (!bel[l_zh])	{puts("0");return 0;}
	
	if (made_l==1)	{
		dp[1][bel[l_zh]]=1;
		
		for (int A=2;A<=n;++A)
			for (int i=1;i<=top_cal;++i)	//这一行状态 
				for (int j=1;j<=top_cal;++j)	//上一行状态 
					if (jud[i][j])	dp[A][i]=(dp[A-1][j]%mod+dp[A][i]%mod)%mod;
		
		int ans=0;
		for (int i=1;i<=top_cal;++i)
			ans=(ans%mod+dp[n][i]%mod)%mod;
		printf("%d\n",ans);
	}
	
	else {
		
		for (int i=1;i<=top_cal;++i)
			dp[1][i]=1;
		
		for (int A=2;A<=n;++A){
			if (A==made_l){
				for (int j=1;j<=top_cal;++j)
					if (jud[bel[l_zh]][j])	dp[A][bel[l_zh]]=(dp[A-1][j]%mod+dp[A][bel[l_zh]]%mod)%mod;
				continue;
			}
			for (int i=1;i<=top_cal;++i)//这一行的状态 
				for (int j=1;j<=top_cal;++j)//上一行的状态 
					if (jud[i][j])	dp[A][i]=(dp[A-1][j]%mod+dp[A][i]%mod)%mod;
		}
		
		int ans=0;
		for (int i=1;i<=top_cal;++i)
			ans=(ans%mod+dp[n][i]%mod)%mod;
		printf("%d\n",ans);
		
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值