版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38786088/article/details/82721657
最近两场比赛,都涉及到此类问题!以前没注意过这种问题,都在状态转移方程推出后,止步于n太大!事后总结,线性递推可以用矩阵来表示,进而快速幂解决n太大的问题!
题意:n×m矩阵填黑白两色,行相同相邻两个不能同为白色,相邻两列不全为黑色
(n≤5,m≤1e18),问填色的方案数mod 1e9+7的值.
令黑色为1,白色为0,将一列的颜色01由下往上排布,即An-1An-2……A0
(Ai为1表示该列第i+1格子为黑色,0即为白色)
也就是2进制表示,00……0~11……1,每列有2^n种可能出现的值。(状态)
(每一列的长度最长为5,即状态数最多为32,2的5次方)
限制条件:
相邻两列不全为黑色—>第i列为111……1,第i+1列不能为状态111……1
相邻两列的同行格不同为白色—>第i列的j位为0,第i+1列的j位不为0
相邻状态关系结论:
前面状态为x,相邻状态为y。
x、y满足 x|y = 11……1(2进制),(x=y=11……1除外)
状态转移方程:
f[x][m] 定义:第m列涂色状态为x,前面1~m列染色满足限制条件的方案数
f[x][m]=(0≤y≤2n−1,x∣y=2n−1,x=y̸=2n−1)∑f[y][m−1],(0≤x≤2n−1)
举个例子: n=2时,每列有4种状态,枚举第m列的状态
1.状态为0,f[0][m]=f[3][m−1]
(第m列全为白色,不能同数位为0,第m−1列全为黑色)
2.状态为1,f[1][m]=f[2][m−1]+f[3][m−1]
(第m列第2格为白色,第m−1列第2格必须为黑色,第1格可黑可白)
3.状态为2,f[2][m]=f[1][m−1]+f[3][m−1]
(第m列第1格为白色,第m−1列第1格必须为黑色,第2格可黑可白)
4.状态为3,f[3][m]=f[0][m−1]+f[1][m−1]+f[2][m−1]
(第m列全为黑色,即只要第m−1列不全为黑色即可)
整理如下:
⎩⎪⎪⎪⎨⎪⎪⎪⎧f[0][m]f[1][m]f[2][m]f[3][m]=f[3][m−1]=f[2][m−1]+f[3][m−1]=f[1][m−1]+f[3][m−1]=f[0][m−1]+f[1][m−1]+f[2][m−1]
当m不大时,可以去递推,但m超大时,必须另外想方法,因为是线性递推,而且递推状态不多,可以矩阵表示,考虑矩阵快速幂
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][m]f[1][m]f[2][m]f[3][m]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡0001001101011110⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤∗⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][m−1]f[1][m−1]f[2][m−1]f[3][m−1]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
递推得到:
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][m]f[1][m]f[2][m]f[3][m]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡0001001101011110⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤m−1∗⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][1]f[1][1]f[2][1]f[3][1]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][1]f[1][1]f[2][1]f[3][1]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡1111⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
所以你只需要获取矩阵系数即可!
#include <bits/stdc++.h>
#define llt long long
using namespace std;
const int N=40;
const llt mod = 1e9+7;
llt a[N][N],ans[N][N],tmp[N][N];
void multi(llt x[][N],llt y[][N],int len){
for(int i=0;i<len;++i)
for(int j=0;j<len;++j){
tmp[i][j] = 0;
for(int k=0;k<len;++k)
tmp[i][j] += x[i][k]*y[k][j]%mod;
tmp[i][j] %= mod;
}
for(int i=0;i<len;++i)
for(int j=0;j<len;++j)
x[i][j] = tmp[i][j];
}
void quick_mod(llt x,int len){
for(;x;x>>=1,multi(a,a,len))
if(x&1) multi(ans,a,len);
}
int main(){
int n;
llt m;
scanf("%d%lld",&n,&m);
int status = 1<<n;
for(int i=0;i<status;++i)
for(int j=0;j<status;++j)
if(i==status-1&&j==status-1)
a[i][j] = 0;
else if((i|j)==status-1)
a[i][j] = 1;
else a[i][j] = 0;
for(int i=0;i<status;++i)
for(int j=0;j<status;++j)
if(i==j) ans[i][j] = 1;
else ans[i][j] = 0;
quick_mod(m-1,status);
llt Ans = 0;
for(int i=0;i<status;++i)
for(int j=0;j<status;++j)
Ans = (Ans+ans[i][j])%mod;
printf("%lld\n",Ans);
return 0;
}
例2:18焦作网络赛 L、Poor God Water
题意: 由字符C、W、F组成的字符串中,任意长度为3的子串不为"CCC"、“MMM”、“FFF”、“MCF”、“FCM”、“CMC”、“CFC”; 问有多少种不同的字符串符合要求,答案mod 1 000 000 007.
即字符串前部分已经构建,影响后面m个字符的枚举的只有其最后两个字符,所以设计dp数组f的含义:前面两个为‘#*’,后面长度为n的满足要求的不同字符串的个数;状态转移即枚举接下来的字符C、M、F,删去非法的,上面图片也就是状态转移过程!将状态编号,得到以下状态转移方程
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧f[0][m]f[1][m]f[2][m]f[3][m]f[4][m]f[5][m]f[6][m]f[7][m]f[8][m]=f[1][m−1]+f[2][m−1]=f[4][m−1]+f[5][m−1]=f[7][m−1]+f[8][m−1]=f[0][m−1]+f[1][m−1]=f[3][m−1]+f[5][m−1]=f[6][m−1]+f[7][m−1]+f[8][m−1]=f[0][m−1]+f[2][m−1]=f[3][m−1]+f[4][m−1]+f[5][m−1]=f[6][m−1]+f[7][m−1]
矩阵表达式:
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][m]f[1][m]f[2][m]f[3][m]f[4][m]f[5][m]f[6][m]f[7][m]f[8][m]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡000100100100100000100000100000010010010000010010010010000001001001001001001001000⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤∗⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][m−1]f[1][m−1]f[2][m−1]f[3][m−1]f[4][m−1]f[5][m−1]f[6][m−1]f[7][m−1]f[8][m−1]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
递推得到:
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][m]f[1][m]f[2][m]f[3][m]f[4][m]f[5][m]f[6][m]f[7][m]f[8][m]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡000100100100100000100000100000010010010000010010010010000001001001001001001001000⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤m∗⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][0]f[1][0]f[2][0]f[3][0]f[4][0]f[5][0]f[6][0]f[7][0]f[8][0]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡f[0][0]f[1][0]f[2][0]f[3][0]f[4][0]f[5][0]f[6][0]f[7][0]f[8][0]⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡111111111⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
即快速矩阵幂得到系数矩阵的m次方后,即矩阵的所有元素之和即为答案!
/*
0 CC --> CM,CF 1,2
1 CM --> MM,MF 4,5
2 CF --> FM,FF 7,8
3 MC --> CC,CM 0,1
4 MM --> MC,MF 3,5
5 MF --> FC,FM,FF 6,7,8
6 FC --> CC,CF 0,2
7 FM --> MC,MM,MF 3,4,5
8 FF --> FC,FM 6,7
*/
#include <bits/stdc++.h>
#define llt long long
#define mp make_pair
#define fi first
#define se second
using namespace std;
const llt mod = 1e9+7;
const int N = 20;
const int maxn = 9;
llt tmp[N][N],ans[N][N],a[N][N];
llt A[N][N]={
{0,1,1,0,0,0,0,0,0},
{0,0,0,0,1,1,0,0,0},
{0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0},
{0,0,0,1,0,1,0,0,0},
{0,0,0,0,0,0,1,1,1},
{1,0,1,0,0,0,0,0,0},
{0,0,0,1,1,1,0,0,0},
{0,0,0,0,0,0,1,1,0},
};
void multi(llt a[][N],llt b[][N],int n){
for(int i=0;i<n;++i)
for(int j=0;j<n;++j){
tmp[i][j] = 0;
for(int k=0;k<n;++k)
(tmp[i][j]+=a[i][k]*b[k][j]%mod)%=mod;
}
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
a[i][j] = tmp[i][j];
}
void quick_mod(llt m,int n){
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
ans[i][j] = (i==j),a[i][j]=A[i][j];
for(;m;m>>=1,multi(a,a,n))
if(m&1) multi(ans,a,n);
}
int main(){
int T;
scanf("%d",&T);
while(T--){
llt n;
scanf("%lld",&n);
if(n==1) printf("3\n");
else{
quick_mod(n-2,maxn);
// for(int i=0;i<maxn;++i)
// for(int j=0;j<maxn;++j)
// printf("%lld%c",ans[i][j]," \n"[j==maxn-1]);
llt res = 0;
for(int i=0;i<maxn;++i)
for(int j=0;j<maxn;++j)
(res+=ans[i][j])%=mod;
printf("%lld\n",res);
}
}
return 0;
}