[COCI2015-2016#6] PAROVI | 互质覆盖 题解
前言
不能在同一个坑上栽第三次!
题意简述
有趣的事实
正解可以做到
题目分析
我们有理论
可以证明,值域在 我还以为不多呢。
我们知道
据说欧拉又解决了
明明只有
#include <cstdio> #include <utility> const int N = 10010; int n, head = 1, tail; std::pair<int, int> Q[N * N]; signed main() { n = 10000; Q[++tail] = { 1, 1 }, ++head; Q[++tail] = { 1, 2 }; while (head <= tail) { auto &[x, y] = Q[head++]; if (x + y > n) continue; Q[++tail] = { y, x + y }; Q[++tail] = { x, x + y }; } printf("%d", tail); return 0; }
于是,从
计算肯定是通过 DP 的方式。阶段显然是当前决策到了第几条线段,但是状态是什么?也就是说怎么刻画一个局面?使用「已经完全覆盖了
对于线段覆盖类问题,我们一般按照某一端点进行排序,左右端点是本质相同的,这里我们不妨按照左端点从小到大考虑。这对我们的状态设计有什么帮助呢?我们发现,当我们按照左端点排过序后决策,一个最终可能合法的局面,在任意时刻都覆盖了数轴的一段连续前缀。这样考虑,如果当前选择了某一条线段,和上一次之间留有空隙,那么这个空隙在之后都不会被填上,因为之后的线段的左端点不小于当前线段。所以可以使用这种状态。
状态设计好后,转移方程应该信手拈来了。我们再明确一下,记
。
正如上文所言,不能选择当前线段。 :
拼上当前线段后,覆盖的前缀增长为 ,故 。 :
当前线段选择后,不会改变覆盖的前缀,故 。
边界
已经能够单次
for (int i = 1; i <= n; ++i) { for (int j = i + 1; j <= n; ++j) if (cop[i][j]) { // i and j are coprimes for (int o = j; o <= n; ++o) f[o] = add(f[o], f[o]); for (int o = i; o < j; ++o) f[j] = add(f[j], f[o]); } }
我们发现,这就是一个后缀区间乘
倘若不看别的,但看这个乘
但看这个区间求和,也可以线性扫一遍,同时维护
两者,显然可以融合。唯一要小心处理的是
for (int i = 1; i <= n; ++i) { int sum = 0; for (int j = n; j >= i; --j) toadd(sum, f[j]); for (int j = n; j > i; --j) { tosub(sum, f[j]); if (cop[i][j]) toadd(f[j], f[j]), toadd(f[j], sum); } for (int j = i + 1, tag = 1; j <= n; ++j) { tomul(f[j], tag); if (cop[i][j]) toadd(tag, tag); } }
于是,我们可以以严格
代码
#include <cstdio> #include <utility> const short N = 10010; short n; int mod; int head = 1, tail; std::pair<short, short> Q[N * N]; bool cop[N][N]; int f[N]; inline void toadd(int& a, const int& b) { (a += b) >= mod && (a -= mod); } inline void tosub(int& a, const int& b) { (a -= b) < 0 && (a += mod); } inline void tomul(int& a, const int& b) { a = 1ll * a * b % mod; } signed main() { scanf("%hd%d", &n, &mod); Q[++tail] = { 1, 1 }, ++head; Q[++tail] = { 1, 2 }; while (head <= tail) { auto &[x, y] = Q[head++]; if (x + y > n) continue; Q[++tail] = { x, x + y }; Q[++tail] = { y, x + y }; } for (int i = 1; i <= tail; ++i) cop[Q[i].first][Q[i].second] = true; f[1] = 1; for (int i = 1; i <= n; ++i) { int sum = 0; for (int j = n; j >= i; --j) toadd(sum, f[j]); for (int j = n; j > i; --j) { tosub(sum, f[j]); if (cop[i][j]) toadd(f[j], f[j]), toadd(f[j], sum); } for (int j = i + 1, tag = 1; j <= n; ++j) { tomul(f[j], tag); if (cop[i][j]) toadd(tag, tag); } } printf("%d", f[n]); return 0; }
Generator
if (argc < 4) { cout << "Usage: " << args[0] << " NMAX MODMAX FORCEMODE SEED" << endl; return 0; } if (string(args[3]) != "true" && string(args[3]) != "false") { cout << "FORCEMODE should be 'true' or 'false'" << endl; return 0; } int NMAX = stoi(string(args[1])); int MODMAX = stoi(string(args[2])); bool FORCEMODE = string(args[3]) == "true"; int SEED = stoi(string(args[4])); mt19937 rand_num(SEED); static auto rand = [&] (int l, int r) -> int { uniform_int_distribution<int> dist(l, r); return dist(rand_num); }; cout << (FORCEMODE ? NMAX : rand(max(2, NMAX - 234), NMAX)) << ' ' << (FORCEMODE ? MODMAX : rand(max(2, MODMAX - 234), MODMAX)) << endl;
数据范围
const int NMAX[50] = { 5, 8, 13, 18, 20, 50, 60, 80, 90, 100, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000 }; const int MODMAX[50] = { 1000000000, 1000000000, 1000000000, 1000000000, 1000000000, 2, 20, 200, 2000, 20000, 114, 514, 19, 19, 810, 10000, 10000, 10000, 10000, 10000, 100000, 100000, 1000000, 10000000, 100000000, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, 1000000007, }; const bool FORCEMODE[50] = { true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true };
反思
线段覆盖类问题,这种处理方法是套路的。做完这题可以尝试 [USACO20FEB] Help Yourself P。
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18559166。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】