据说是能找到的题当中,最简单的三进制状压DP
题目大意
给 n ∗ m n * m n∗m 大的格子, 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(10000∗486),如果把每两个连接起来合法的情况当成连出来一个边,则一共相当于是 486 486 486 条。