专栏文章

题解:P7800 [COCI 2015/2016 #6] PAROVI

P7800题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mip3zljh
此快照首次捕获于
2025/12/03 05:47
3 个月前
此快照最后确认于
2025/12/03 05:47
3 个月前
查看原文
Tag:DP
把问题转换为:选取一些 [1,n][1,n] 的子区间,它们的端点编号互质,且这些区间覆盖 [1,n][1, n] 的方案数(区间 [l,k][l,k][k+1,r][k+1,r] 不算覆盖 [l,r][l,r])。
我们先预处理所有合法区间,复杂度 O(n2logn)O(n^2\log n)
考虑怎么描述合法方案:从前缀 11 开始,不断往后面接区间,保证后面接的区间 "左端点 \le 当前前缀的右端点,右端点 >> 当前前缀的右端点";然后中间还有一些被包含的无用区间,它们选不选都不影响合法性,也需要算上方案。
针对这个构造方法,我们可以先把所有区间按照左端点升序排序。
那么我们现在考虑怎么不重不漏地计算:
  1. 直接模拟构造的过程,设 f(i,j)f(i,j) 表示考虑了前 ii 个区间,覆盖了前缀 [1,j][1,j],且 k>j\forall k>j,都有前缀 [1,k][1,k] 没被覆盖,的方案数。
  2. 假设当前考虑区间 [l,r][l,r]
    • ljl\le j,则 f(i,j)f(i+1,max{j,r})f(i,j)\to f(i+1,\max\{j,r\}),代表要么往后接一段,要么添加一个无用区间。
    • ljl\le j 时也可以不选这个区间,f(i,j)f(i+1,j)f(i,j)\to f(i+1,j)
    • l>jl>j,添加这个区间不会使覆盖的前缀增加,则 f(i,j)f(i+1,j)f(i,j)\to f(i+1,j)
复杂度上界 O(n3)O(n^3)
CPP
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;

const int N = 25, MOD = 1e9;

int gcd(int a, int b) {
	if (a < b) swap(a, b);
	if (!b) return a;
	return gcd(b, a % b);
}

int n, m, f[N * N][N];
vector<PII> rec;

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		for (int j = i + 1; j <= n; j++) {
			if (gcd(i, j) == 1) {
				rec.push_back({i, j});
			}
		}
	}
	for (auto [x, y] : rec);
	m = rec.size();
	sort(rec.begin(), rec.end(), [&](auto i, auto j) { return i.first == j.first ? i.second < j.second : i.first < j.first; });
	f[0][1] = 1;
	for (int i = 0; i < m; i++) {
		for (int j = 1; j <= n; j++) {
			if (f[i][j]) {
				(f[i + 1][j] += f[i][j]) %= MOD;
				if (rec[i].first > j) continue;
				(f[i + 1][max(j, rec[i].second)] += f[i][j]) %= MOD;
			}
		}
	}
	cout << f[m][n] << '\n';
}

int main() {
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	solve();
	return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...