开局一张图

image


加的一个群里有人问了这个题 , 写了一下.

\(sol :\) 针对于蓝色的特殊情况很快我们想到当前要填的颜色与前一个颜色和再前一个有关 , 所以就有了方程 f[i][0/1/2][0/1/2] , 其中 \(i\) 表示第 \(i\) 次选择 , 第二维表示 \(i-2\) 次填的颜色 , 第三维表示 \(i-1\) 次填写的颜色 , 转移时对蓝色情况特判 , 初始化时注意蓝色不能开头 , 统计答案时也要将蓝色结尾的情况去除 .

o( \(8 \times n\) )

#include<iostream>
#include<cstdio>
using namespace std;
int n,ans,f[101][3][3];
//0为白色 1为蓝色 2为红色. 
int main()
{
	scanf("%d",&n);
	f[2][0][1] = f[2][0][2] = 1;
	f[2][2][0] = f[2][2][1] = 1;
	for(int pos=3;pos<=n;++pos){
		for(int now=0;now<=2;++now){
			for(int pre1=0;pre1<=2;++pre1){
				for(int pre2=0;pre2<=2;++pre2){
					if(pre1==pre2||pre2==now) continue;
					if(pre2==1){
						if((pre1==0&&now==2)||(pre1==2&&now==0))
							f[pos][pre2][now] += f[pos-1][pre1][pre2];
					}
					else f[pos][pre2][now] += f[pos-1][pre1][pre2];
				}
			}
		}
	}
	for(int i=0;i<=2;++i)
		for(int j=0;j<=2;++j){
			if(i == j||j == 1) continue;
			if(j == 1) continue;
			ans += f[n][i][j];
		}
	printf("%d",ans);
	return 0;
}

很快就有人提出 f[i] = f[i-1] + f[i-2] . 初始化 f[1] = 2,f[2] = 2,f[3] = 4 .

加上 f[i-1] 是因为蓝色不能结尾 , 那么 \(i-1\) 的结尾为红色 , 那么 \(i\) 位必须为白 ; \(i-1\) 为白色同理.

加上 f[i-2] 是因为我们要加上 \(i-1\) 为蓝色的情况 (这对 f[i-1] 没有贡献 , 但对 f[i] 有贡献) , 那么 \(i-2\) 位与 \(i\) 位根据题目要求是一一对应的 , 所以直接加就好了.

手摸一下 f[2] , f[3] 怎么推 f[4] = 6 就懂了.

这样 \(o(n)\) 递推即可.


\(n\)\(1e8\) 以上的数 ? 无疑我们想到矩阵快速幂 , 对 \(DP\) 方程的转移加速.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int mod = 10000;
int n,f[2][3],a[3][3];
void recovery(){
	f[1][1] = 0,f[1][2] = 1;
	a[1][1] = 0,a[1][2] = 1;
	a[2][1] = 1,a[2][2] = 1;
}
void work1(int f[2][3],int a[3][3]){
	int t[2][3];
	memset(t,0,sizeof(t));
	for(int i=1;i<=1;++i)
		for(int j=1;j<=2;++j)
			for(int k=1;k<=2;++k)
				t[i][j] = (t[i][j]%mod + (long long)f[i][k]*a[k][j]%mod)%mod;
	memcpy(f,t,sizeof(t));
}
void work2(int a[3][3]){
	int t[3][3];
	memset(t,0,sizeof(t));
	for(int i=1;i<=2;++i)
		for(int j=1;j<=2;++j)
			for(int k=1;k<=2;++k)
				t[i][j] = (t[i][j]%mod + (long long)a[i][k]*a[k][j]%mod)%mod;
	memcpy(a,t,sizeof(t));
}
int qsm(int y){
	if(y == 1) return 0;
	if(y == 2) return 1;
	y -= 2;	
	while(y){
		if(y&1) work1(f,a);
		work2(a);
		y>>=1;
	}
	return f[1][2];
}
int main()
{
	recovery();
	printf("%d\n",qsm(n));
	return 0;
}

今天顺带复习了一下矩阵快速幂 , 中间有一个疑问 , 过程矩阵 \(f1 \times a \times a...\)(有 \(n\)\(a\)) , 根据矩阵乘法有结合律 , 上式可化为\(f1 \times a^n\) , 也就是自我矩乘 \(n\) 次 , 那么代码中为什么会有 \(f\) 呢? 联系快速幂 , \(f\) 相当于 \(ans\) 去累积每次 \(2\) 的幂次方值 , 相当于初始矩阵(对角线为 \(1\) ,相当于原快速幂的 \(ans=1\) )累积每次 \(2\) 的幂次方 , 最后再乘 \(f1\) , 显然这步是可以省略的.

posted @ 2021-10-16 10:10  xqy2003  阅读(23)  评论(0编辑  收藏  举报