【ybtoj高效进阶 21279】排列计数(矩阵乘法)(光速幂)(DP)

排列计数

题目链接:ybtoj高效进阶 21279

题目大意

多次询问,每次问你有多少个长为 n 的排列满足相邻两个的差是 2 一下。

代码

考虑能否 DP,那你想他相差是 \(2\),你考虑从 \(1\sim n\) 的数组一次取走数,每次去的位置间隔不超过 \(2\)

考虑可以怎么走。
\(f_i\) 为取走前 \(i\) 个的方案数。
\(f_{i}=f_{i-1}+f_{i-3}\)

第一个是直接走,第二个是那个位置走两个过去,走一个回来,在走两个过去。
然后答案是你可能按上面走了那么多,你就两个两个飞过去,再两个两个飞回来。
所以每个 \(f\) 都要加上。

接着考虑优化 DP。
发现可以矩阵乘法。

|\(0\)|\(1\)|\(0\)|\(0\)|
|--|--|--|--|--|

\(ans\) \(f_{i-1}\) \(f_{i-2}\) \(f_{i-3}\)
1 0 0 0
1 1 1 0
0 0 0 1
0 1 0 0

但是你会发现,答案会多了一点,为什么呢?
因为在 \(f_{n-3}\) 的位置直接跳过来是被重复计算的,不过还好,减去就可以了。

然后矩阵乘法不能直接用快速幂的,要用光速幂。(设 \(x\) 为询问的上界)
光速幂就是把它分成 \(\sqrt{x}\) 以内和 \(\sqrt{x}\) 的倍数这些部分预处理出来。
然后到时查询的时候就是分成这两个部分,就只需要乘两次了。

代码

#include<cstdio>
#define ll long long
#define mo 1000000007

using namespace std;

struct matrix {
	int n, m;
	ll a[5][5];
}a, b[100001], one, c[100001];
int T, n, lst;
ll ans;

matrix operator *(matrix x, matrix y) {
	matrix re;
	re.n = x.n; re.m = y.m;
	for (int i = 1; i <= re.n; i++)
		for (int j = 1; j <= re.m; j++)
			re.a[i][j] = 0;
	for (int k = 1; k <= x.m; k++)
		for (int i = 1; i <= re.n; i++)
			for (int j = 1; j <= re.m; j++)
				re.a[i][j] = (re.a[i][j] + x.a[i][k] * y.a[k][j] % mo) % mo;
	return re;
}

int main() {
//	freopen("per.in", "r", stdin);
//	freopen("per.out", "w", stdout);
	
	one.n = one.m = 4;
	one.a[1][1] = 1; one.a[2][2] = 1; one.a[3][3] = 1; one.a[4][4] = 1;
	
	b[0] = one;
	b[1].n = b[1].m = 4;
	b[1].a[1][1] = 1; b[1].a[1][2] = 0; b[1].a[1][3] = 0; b[1].a[1][4] = 0;
	b[1].a[2][1] = 1; b[1].a[2][2] = 1; b[1].a[2][3] = 1; b[1].a[2][4] = 0;
	b[1].a[3][1] = 0; b[1].a[3][2] = 0; b[1].a[3][3] = 0; b[1].a[3][4] = 1;
	b[1].a[4][1] = 0; b[1].a[4][2] = 1; b[1].a[4][3] = 0; b[1].a[4][4] = 0;
	for (int i = 2; i * i <= 1e9; i++)
		b[i] = b[i - 1] * b[1], lst = i;
	c[0] = one; c[1] = b[lst];
	for (int i = 2; i * lst <= 1e9; i++)
		c[i] = c[i - 1] * c[1]; 
	
	
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n);
		
		if (n <= 1) {
			ans ^= 1;
			continue;
		}
		
		a.n = 1; a.m = 4;
		a.a[1][1] = 0; a.a[1][2] = 1; a.a[1][3] = 0; a.a[1][4] = 0;
		a = a * c[n / lst] * b[n % lst];
		
		ans ^= (a.a[1][1] - a.a[1][4] + mo) % mo;
	}
	
	printf("%lld", ans);
	
	return 0;
}
posted @ 2021-10-27 07:20  あおいSakura  阅读(32)  评论(0编辑  收藏  举报