CF1780F Three Chairs
知识点:枚举,容斥。
原题面:https://codeforces.com/contest/1780/problem/F。
涉及 的枚举的常见套路 + 似乎不太常见的容斥(?)
以及一种和题解原理相同但更好写的写法。
省流版:容斥的本质是把一个不重不漏的集合的并的贡献,转化成多个集合的交的贡献的和。
简述
给定一长度为 的数列 ,保证所有数两两不同。求满足: 的三元组 的数量。
,。
1S,256MB。
分析
先排序,以下钦定三元组 中 。然后考虑枚举三元组中的最大值 和最小值 ,如果 ,那么它们对答案的贡献显然为可供选择的 的数量:,则仅需统计 这种互质对的贡献。
考虑求得一个数组 , 代表以 为较大元素的满足条件的三元组的数量。以 为较大元素的数对总数为 ,考虑问题的反面求非互质对的数量。套路地考虑枚举质因数 ,检查 中是否存在 的倍数,并令较大的倍数减去较小的倍数的贡献。具体地,对于小于 的 个与 不互质的 , 的贡献应减去 。
但在枚举质因数 的过程中,可能会出现 重复统计的情况。比如 和 均是 的约数,使得 的贡献被 的倍数减去了两次。
考虑容斥。考虑扩大枚举 的范围,对于一对 ,如果它们共同的质因数为 ,那么减去枚举到它们时 对 的贡献后,应再加上枚举到 的时 对 的贡献,再减去枚举到 的时 对 的贡献,……这样可以保证 的贡献最终只会被减去 1 次。总结一下,我们考虑枚举共同的质因数——如果 仅由 个质数的一次方构成,那么枚举到 时, 对 贡献的符号应为 ,否则枚举到 时不统计贡献。
原理的话……学艺不精没法很好地解释。可以考虑质因数分解为 中任意非空子集的数的集合 ,满足:
上式中 即 应当减去贡献的 的不重不漏的集合,我们的目标是把它们的贡献仅减去 1 次。考虑这个经典式子的意义,达成上述目的,等效于把所有集合 的贡献减去 次。
表示作为 的倍数的 的集合。而 共有 种。我们不妨把上面的式子写的更抽象一点:
即由 个质数的一次方构成的 的个数。由这个式子可知,令 减去所有 的贡献 1 次,等效于先枚举 ,并令 减去所有作为 的倍数的 的贡献 次。
实现时使用埃氏筛,处理出每个数的质因数同时标记不符合枚举条件的 ,再枚举合法的 的倍数更新即可。
总复杂度为 级别。
代码
复制复制//By:Luckyblock /* */ #include <cstdio> #include <cctype> #include <vector> #include <algorithm> #define LL long long const int kN = 3e5 + 10; //============================================================= int n, a[kN], pos[kN]; LL ans, f[kN]; bool vis[kN], vis1[kN]; std::vector <int> p[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0'; return f * w; } //============================================================= int main() { // freopen("1.txt", "r", stdin); n = read(); for (int i = 1; i <= n; ++ i) { a[i] = read(); if (i >= 3) f[i] = 1ll * (i - 2) * (i - 1) / 2ll; } std::sort(a + 1, a + n + 1); for (int i = 1; i <= n; ++ i) pos[a[i]] = i; for (int i = 2; i <= a[n]; ++ i) { if (vis1[i]) continue; if (!vis[i]) p[i].push_back(i); int sz = p[i].size(); LL cnt = (pos[i] > 0), sum = pos[i]; for (int j = 2; i * j <= a[n]; ++ j) { vis[i * j] = 1; if (!vis[i]) p[i * j].push_back(i); if (j % i == 0) vis1[i * j] = 1; if (pos[i * j]) { f[pos[i * j]] += (sz % 2 ? -1 : 1) * (cnt * (pos[i * j] - 1ll) - sum); ++ cnt, sum += pos[i * j]; } } } for (int i = 1; i <= n; ++ i) ans += f[i]; printf("%lld\n", ans); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】