【bzoj5082】弗拉格 矩阵乘法

题目描述

给你n个flag,你要把每个染色成红黑白黄四色之一,满足:
1.相邻旗不能同色
2.白不能和黄相邻,红不能和黑相邻
3.不能存在连续三个球依次是“黑白红”或“红白黑”
4.翻转后相等视为等价
设不等价方案数为f(n),给定l,r,求
Sigma f(i),其中L<=i<=R模1000000007

输入

输入两个数l,r
l, r ≤ 10^9

输出

输出答案

样例输入

3 4

样例输出

23


题解

矩阵乘法

容易设出dp状态 $f[i][j][k]$ 表示前 $i$ 个flag,最后一个的颜色为 $j$ ,倒数第二个的颜色为 $k$ 的方案数。

显然这个dp方程可以使用矩阵乘法来加速转移,并使用计数器维护前缀和。

至于翻转后相等视为等价的问题,易知:答案=(总方案数+翻转后与原来相等的方案数)/2。于是求出反转后与原来相等的方案数即可。

容易发现偶数长度的中间两个一定相同,因此不存在偶数长度的回文串。

对于奇数长度,发现题目条件的限制是对称的(AB<=>BA,ABC<=>CBA),因此某长度为 $2k-1$ 的奇数长度回文串的个数即为长度为 $k$ 的串的个数。再次求 $lceilfrac n2 ceil$ 的答案即可。

最后前缀相减即为最终答案。

时间复杂度 $O(9^3log n)$

#include <cstdio>
#include <cstring>
#include <algorithm>
#define mod 1000000007
using namespace std;
typedef long long ll;
struct data
{
	ll v[9][9];
	data() {memset(v , 0 , sizeof(v));}
	ll *operator[](int a) {return v[a];}
	data operator*(data &a)
	{
		data ans;
		int i , j , k;
		for(i = 0 ; i <= 8 ; i ++ )
			for(j = 0 ; j <= 8 ; j ++ )
				for(k = 0 ; k <= 8 ; k ++ )
					ans[i][j] = (ans[i][j] + v[i][k] * a[k][j]) % mod;
		return ans;
	}
}A;
data pow(data x , int y)
{
	data ans;
	int i;
	for(i = 0 ; i <= 8 ; i ++ ) ans[i][i] = 1;
	while(y)
	{
		if(y & 1) ans = ans * x;
		x = x * x , y >>= 1;
	}
	return ans;
}
void init()
{
	int i;
	for(i = 0 ; i <= 8 ; i ++ ) A[i][0] = 1;
	A[1][5] = A[1][7] = 1;
	A[2][5] = A[2][7] = 1;
	A[3][6] = A[3][8] = 1;
	A[4][6] = A[4][8] = 1;
	A[5][1] = A[5][3] = 1;
	A[6][1] = A[6][3] = 1;
	A[7][2] = 1;
	A[8][4] = 1;
}
ll calc(int x)
{
	if(!x) return 0;
	data T = pow(A , x - 1);
	ll ans = T[0][0] * 4;
	int i;
	for(i = 1 ; i <= 8 ; i ++ ) ans += T[i][0];
	return ans % mod;
}
int main()
{
	int l , r;
	scanf("%d%d" , &l , &r) , l -- ;
	init();
	printf("%lld
" , ((calc(r) + calc((r + 1) / 2) - calc(l) - calc((l + 1) / 2)) * 500000004 % mod + mod) % mod);
	return 0;
}
原文地址:https://www.cnblogs.com/GXZlegend/p/7886580.html