1. 程式人生 > >【bzoj5082】弗拉格 矩陣乘法

【bzoj5082】弗拉格 矩陣乘法

algorithm ret 染色 輸出 git led style 顏色 狀態

題目描述

給你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$ 的串的個數。再次求 $\lceil\frac n2\rceil$ 的答案即可。

最後前綴相減即為最終答案。

時間復雜度 $O(9^3\log n)$

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define N 1010
using namespace std;
int n , m , a[N][N] , c[N][N] , v[N] , tot;
bool solve(int mid)
{
	int i , j , k;
	for(i = 1 ; i <= m ; i ++ )
		for(j = i + 1 ; j <= m ; j ++ )
			c[i][j] = 0;
	for(i = 1 ; i <= n ; i ++ )
	{
		tot = 0;
		for(j = 1 ; j <= m ; j ++ )
			if(a[i][j] >= mid)
				v[++tot] = j;
		for(j = 1 ; j <= tot ; j ++ )
		{
			for(k = j + 1 ; k <= tot ; k ++ )
			{
				if(c[v[j]][v[k]]) return 1;
				c[v[j]][v[k]] = 1;
			}
		}
	}
	return 0;
}
inline char nc()
{
	static char buf[100000] , *p1 , *p2;
	return p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 100000 , stdin) , p1 == p2) ? EOF : *p1 ++ ;
}
inline int read()
{
	int ret = 0; char ch = nc();
	while(!isdigit(ch)) ch = nc();
	while(isdigit(ch)) ret = ((ret + (ret << 2)) << 1) + (ch ^ ‘0‘) , ch = nc();
	return ret;
}
int main()
{
	int i , j , l = 1 << 30 , r = 0 , mid , ans;
	n = read() , m = read();
	for(i = 1 ; i <= n ; i ++ )
		for(j = 1 ; j <= m ; j ++ )
			a[i][j] = read() , l = min(l , a[i][j]) , r = max(r , a[i][j]);
	while(l <= r)
	{
		mid = (l + r) >> 1;
		if(solve(mid)) ans = mid , l = mid + 1;
		else r = mid - 1;
	}
	printf("%d\n" , ans);
	return 0;
}

【bzoj5082】弗拉格 矩陣乘法