CF1780F Three Chairs

知识点:枚举,容斥。

原题面:https://codeforces.com/contest/1780/problem/F

涉及 gcd 的枚举的常见套路 + 似乎不太常见的容斥(?)

以及一种和题解原理相同但更好写的写法。

省流版:容斥的本质是把一个不重不漏的集合的并的贡献,转化成多个集合的交的贡献的和。

简述

给定一长度为 n 的数列 a,保证所有数两两不同。求满足:gcd(min(ai,aj,ak),max(ai,aj,ak))=1 的三元组 (i,j,k) 的数量。
3n3×1051ai3×105
1S,256MB。

分析

先排序,以下钦定三元组 (i,j,k)i<j<k。然后考虑枚举三元组中的最大值 ak 和最小值 ai,如果 gcd(ai,ak)=1,那么它们对答案的贡献显然为可供选择的 aj 的数量:ki1,则仅需统计 (ai,ak) 这种互质对的贡献。

考虑求得一个数组 ffk 代表以 ak 为较大元素的满足条件的三元组的数量。以 ak 为较大元素的数对总数为 (i2)(i1)2,考虑问题的反面求非互质对的数量。套路地考虑枚举质因数 g,检查 a 中是否存在 g 的倍数,并令较大的倍数减去较小的倍数的贡献。具体地,对于小于 akc 个与 ak 不互质的 aifk 的贡献应减去 c×(k1)i

但在枚举质因数 g 的过程中,可能会出现 ai 重复统计的情况。比如 g=2g=3 均是 ai=6 的约数,使得 ai=6 的贡献被 ai 的倍数减去了两次。

考虑容斥。考虑扩大枚举 g 的范围,对于一对 (ai,ak),如果它们共同的质因数为 (p1,p2,,pq),那么减去枚举到它们时 aifk 的贡献后,应再加上枚举到 p1×p2,p1×p3,pq1×pq 的时 aifk 的贡献,再减去枚举到 p1×p2×p3,p1×p2×p4,pq2×pq1×pq 的时 aifk 的贡献,……这样可以保证 ai 的贡献最终只会被减去 1 次。总结一下,我们考虑枚举共同的质因数——如果 g 仅由 l 个质数的一次方构成,那么枚举到 g 时,aifk 贡献的符号应为 (1)l,否则枚举到 g 时不统计贡献。


原理的话……学艺不精没法很好地解释。可以考虑质因数分解为 (p1,p2,,pq) 中任意非空子集的数的集合 {ai},满足:

Al={x(pix)(xa)}

{ai}=|l=1qAl|=l=1n(1)l+1(1u1<<ulq|Au1Aul|)

上式中 |l=1qAl|fk 应当减去贡献的 ai不重不漏的集合,我们的目标是把它们的贡献仅减去 1 次。考虑这个经典式子的意义,达成上述目的,等效于把所有集合 |Au1Aul| 的贡献减去 (1)l 次。

|Au1Aul| 表示作为 pu1×,,pul 的倍数的 ai 的集合。而 pu1×,,pul 共有 Cql 种。我们不妨把上面的式子写的更抽象一点:

1=l=1q(1)l+1Cql=l=1q(1)lCql

Cql 即由 l 个质数的一次方构成的 g 的个数。由这个式子可知,令 fk 减去所有 ai 的贡献 1 次,等效于先枚举 g,并令 fk 减去所有作为 g 的倍数的 ai 的贡献 (1)l 次。


实现时使用埃氏筛,处理出每个数的质因数同时标记不符合枚举条件的 g,再枚举合法的 g 的倍数更新即可。

总复杂度为 O(nlnlnn) 级别。

代码

复制复制
//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;
}
posted @   Luckyblock  阅读(72)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示