【动态规划】之三进制状压DP《涂抹果酱》

据说是能找到的题当中,最简单的三进制状压DP

题目大意

n ∗ m n * m nm 大的格子, n < = 10000 n <= 10000 n<=10000 m < = 5 m<=5 m<=5,每相邻两个格子不能涂抹一样的颜色,一共有1,2,3这三种颜色,给出第k行每个格子涂抹的颜色,求总共有多少涂抹的方案。

补充之找到原题面

y v j yvj yvj 两周年庆典要到了, S a m Sam Sam 想为 T y v j Tyvj Tyvj 做一个大蛋糕。蛋糕俯视图是一个 N × M N×M N×M 的矩形,它被划分成 N × M N×M N×M 个边长为 1 × 1 1×1 1×1 的小正方形区域(可以把蛋糕当成 N N N M M M 列的矩阵)。蛋糕很快做好了,但光秃秃的蛋糕肯定不好看!所以, S a m Sam Sam 要在蛋糕的上表面涂抹果酱。果酱有三种,分别是红果酱、绿果酱、蓝果酱,三种果酱的编号分别为 1 , 2 , 3 1,2,3 1,2,3。为了保证蛋糕的视觉效果, A d m i n Admin Admin 下达了死命令:相邻的区域严禁使用同种果酱。但 S a m Sam Sam 在接到这条命令之前,已经涂好了蛋糕第 K 行的果酱,且无法修改。

现在 S a m Sam Sam 想知道:能令 A d m i n Admin Admin 满意的涂果酱方案有多少种。请输出方案数 m o d    1 0 6 mod \;10^6 mod106。若不存在满足条件的方案,请输出 0 0 0


对于那么小的一个m,我们很容易可以想到状压,但是颜色有三种,所以一般的二进制的状压显然是不可做的,这时候就要手写位运算搞个三进制的,把1到3分别记录成三进制下某一位的0到2,然后其实就看哪两个状态是可以相连的了,存下每种状态及可以相连的状态,易知正着和倒着其实是误差别的。

怎么说呢,我们现在把每一层压成一个数字,接下来的任务就是看合法的序列有多少种,以k为界限分开,上层和下层的方案数分别求出,然后相乘即为总方案数,上层下层互相无影响,所以直接乘法原理就 o k ok ok了。

对于分层啥的,大概就是这样:
在这里插入图片描述
写出来代码就是

#include<bits/stdc++.h>
using namespace std;
#define Mod 1000000
int n,m,maxx,kn[250],k,ans1,ans2,tot,len[250],hh[250][250],f[10010][250],a,lp,ans;
bool jiance(int x)//检测状态本身是否合法
{
	int num=m-1;
	while(num--)
	{
		if(x%3==(x/3)%3) return 0;
		x/=3;
	}
	return 1;
}
bool duibi(int x,int y,int z)//比较两个不同的状态,看是否能相连
{
	while(z--)
	{
		if(x%3==y%3)return 0;
		x/=3;y/=3;
	}
	return 1;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);maxx=1;
	for(int i=1;i<=m;i++)maxx*=3;
	for(int i=0;i<maxx;i++){if(jiance(i)){kn[++tot]=i;}}//枚举maxx种状态是否合法
	for(int i=1;i<=tot;i++)for(int j=i+1;j<=tot;j++)
		if(duibi(kn[i],kn[j],m))
		{//len[i]表示第情况i有多少种方案数
			hh[kn[i]][++len[kn[i]]]=kn[j];
			hh[kn[j]][++len[kn[j]]]=kn[i];//hh[i][j]记录由状态i可以相连的第j个状态
		}
	for(int i=1;i<=m;i++){scanf("%d",&a);lp=lp*3+a-1;}
	f[1][lp]=1;
	int linshi=max(n-k+1,k);
	for(int i=2;i<=linshi;i++)//max(n-k+1,k),根据我上头的“证明”,降低下复杂度
		for(int j=1;j<=tot;j++)//最大数内本身合法的
			for(int g=1;g<=len[kn[j]];g++)//枚举能跟状态j相连的状态 
			{//f[i][j]表示第i层状态为j的合法情况数
				int x=hh[kn[j]][g];
				f[i][x]=(f[i][x]+f[i-1][kn[j]])%Mod;
			}//第i层状态x的数量可以由第i-1层,能与状态x相连的状态转移过来
	for(int i=1;i<=tot;i++)
	{
		ans1=(ans1+f[k][kn[i]])%Mod;//上层每种数为结果的合法方案数
		ans2=(ans2+f[n-k+1][kn[i]])%Mod;//下层的
	}
	cout<<(1ll*ans1*ans2)%Mod;
	return 0;
}

时间复杂度最劣情况下为 O ( 10000 ∗ 486 ) O(10000*486) O(10000486),如果把每两个连接起来合法的情况当成连出来一个边,则一共相当于是 486 486 486 条。

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值