题解:P11626 [迷宫寻路 Round 3] 七连击

节选自:DP做题记录(三)(2025.4.5 - 2025.4.19)

对于一段区间的划分,很容易想到 DP。

我们设 \(f_{i, j}\) 表示把前 \(i\) 个数划分成 \(j\) 段的答案,再设 \(g_{i, j}\) 表示把前 \(i\) 个数划分成 \(j\) 段的方案数。考虑前一个划分点在什么位置,可以得到:

\[g_{i, j} = \displaystyle\sum_{k = 1}^{i - 1} g_{k, j - 1}, g_{0, 0} = 1 \]

我们再考虑划分出的这一段对答案的贡献。如果们固定了一段区间 \([l, i]\),那么这段本身的贡献就是 \(\gcd(a_l, \dots, a_i)\),而 \([1, l - 1]\) 就可以随便划分了,此时:

\[f_{i, j} = \displaystyle\sum_{k = 1}^{i - 1} f_{k, j - 1} + g_{k, j - 1} \times \gcd_{l = k + 1}^i a_l \]

观察可以发现,\(g_{i, j}\) 的转移为上一行一段前缀的和,可以前缀和优化。而 \(f_{i, j}\) 则因为加入了 \(\gcd\) 的操作,不是很好前缀和优化。不过我们考虑当固定了右端点 \(i\),左端点 \(l\) 不断向左移动的过程中,区间 \(\gcd\) 一定是单调不升的,而且每次减小,至少除以了一个 \(2\),那么一定只用 \(O(\log n)\) 次就除到了 \(1\),然后就不会变了。于是我们二分答案找到每次区间 \(\gcd\) 变化的位置,那么左端点从上一个二分出的点 \(x\) 到这一次二分出的点 \(y\) 时,区间 \(\gcd\) 是不变的,那么此时就可以前缀和优化了。设分了 \(k\) 段,复杂度为 \(O(kn \log^2 n)\),足以通过此题。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 9, LOGN = 19, MOD = 998244353;
int st[N][LOGN], LOG2[N], a[N], g[N][9], f[N][9], sumg[N][9], sumf[N][9], n;
void build(){
	LOG2[1] = 0;
	for(int i = 2; i <= n; i++)
		LOG2[i] = LOG2[i >> 1] + 1;
	for(int i = 1; i <= n; i++)
		st[i][0] = a[i];
	for(int j = 1; j <= LOG2[n]; j++)
		for(int i = 1; i + (1 << j) - 1 <= n; i++)
			st[i][j] = __gcd(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
int ask(int l, int r){
	int k = LOG2[r - l + 1];
	return __gcd(st[l][k], st[r - (1 << k) + 1][k]);
}
int find(int l, int r){
	int val = ask(l, r), res = l, tmp = r;
	while(l <= r){
		int mid = (l + r) >> 1;
		if(ask(mid, tmp) == val){
			l = mid + 1;
			res = mid;
		} else
			r = mid - 1;
	}
	return res;
}
signed main(){
	scanf("%lld", &n);
	for(int i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
	build();
	g[0][0] = sumg[0][0] = 1;
	for(int i = 1; i <= n; i++){
		g[i][0] = 1, sumg[i][0] = 1;
		for(int j = 1; j <= 7; j++){
			g[i][j] = sumg[i - 1][j - 1];
			sumg[i][j] = (sumg[i - 1][j] + g[i][j]) % MOD;
		}	
	}
	for(int i = 1; i <= n; i++){
		f[i][1] = ask(1, i);
		sumf[i][1] = (sumf[i - 1][1] + f[i][1]) % MOD;
		for(int l = 1, r; l <= i; l = r + 1){
			r = find(l, i);
			for(int j = 2; j <= 7; j++){
				if(!f[i][j]) f[i][j] = sumf[i - 1][j - 1];//wa *2
				f[i][j] = (f[i][j] + ask(l, i) * (sumg[r - 1][j - 1] - sumg[l - 2][j - 1] + MOD) % MOD) % MOD;
				sumf[i][j] = (sumf[i - 1][j] + f[i][j]) % MOD;
			}
		}
	}
	printf("%lld", sumf[n][7]);
	return 0;
} 
posted @ 2025-04-17 11:20  Orange_new  阅读(42)  评论(0)    收藏  举报