CF1884D Counting Rhyme 题解

news/2024/11/19 19:42:08/文章来源:https://www.cnblogs.com/Athanasy/p/18197630

题目链接:CF 或者 洛谷

给个莫反题解,讲讲常规套路

题目要求满足没有 \(a_k \mid a_i 与 a_k \mid a_j\)\((i,j)\) 的对数,显然即不存在 \(a_k \mid \gcd(a_i,a_j)\)。稍微拓展下,如果不存在整除多个数,那么显然不整除它们的 \(\gcd\) 即可,因为它们的公因数即为满足的最大数,如果为它的因数,则一定满足整除结论。反过来,如果是 \(a_i \mid a_k\)\(a_j \mid a_k\),则应该是 $lcm(a_i,a_j)\mid a_k $,常见结论应该灵活记忆。

那么考虑 \(a_i\le 1e6\),我们可以预处理出 \(cnt[x]\),表示 \(x\) 因子出现的次数。这样一来我们就可以判断 \(\gcd(a_i,a_j)\) 中的因子是否在原数组 \(a\) 当中是否出现过,我们可以用调和级数预处理出 \(1 \sim 1e6\) 中的每个数的因子,然后这样原来的数组中的数就可以进行分解到哈希桶中了,又因为每个数的因子数量大概为开立方级别,所以这部分的总复杂度是对数级别的。(作图网站)

那么我们考虑枚举 \((i,j)\),先写出暴力表达式:

\[ans=\sum_{i=1}^{n}\sum_{j=i+1}^{n}f(\gcd(a_i,a_j)) \]

其中,\(f(x)\) 表示 \(x\) 的因数是否出现过,如果出现过则为 \(1\),否则为 \(0\)。我们考虑枚举 \(x\) 的因数 \(x_i\),则可以写出:

\[f(x)=\prod_{i=1}^{t}[x_i=0] \]

如果 \(x_i=0\),说明它没有这个因数贡献为 \(1\),如果有 \(0\),无论其他因数是否存在,它的贡献都为 \(0\) 了。

这个算法的复杂度大概为:\(O(n^2\ln{n})\)

直接求没办法变形,我们考虑莫反当中的变为枚举每个 \(\gcd\) 的贡献,考虑每种 \((i,j)\) 在每种 \(\gcd\) 当中的贡献。令 \(d=\gcd(a_i,a_j)\)

\[ans=\sum_{i=1}^{n}\sum_{j=i+1}^{n}\sum_{d=1}^{n}f(d)\times[\gcd(a_i,a_j)=d] \]

对一些莫反不熟的朋友稍微介绍下变换思路,原先我们的思路是枚举 \((i,j)\),然后看 \(f(\gcd(a_i,a_j))\) 的贡献。现在我们枚举 \(\gcd(a_i,a_j)=d\),对于 \(d\) 来说,已经知道它的 \(f(d)\) 值,我们只需要统计有多少 \(f(d)\) 即可,即考虑有多少个 \((i,j)\) 满足 \(\gcd(a_i,a_j)=d\),当然如果不满足的,显然贡献为 \(0\),所以上式就是这么来的了。

这个式子不会变形的可以做做:P2398。

常规变形,我们化简为 \(\gcd(a_i,a_j)=1\) 的贡献即:

先将枚举 \(d\) 放在外面:

\[ans=\sum_{d=1}^{n}f(d)\sum_{i=1}^{n}\sum_{j=i+1}^{n}[\gcd(a_i,a_j)=d] \]

然后我们考虑解决 \(ans(i<j)+ans(i>j)+ans(i=j)=ans\),而当 \(i=j\) 时显然有 \(\gcd(a_i,a_j)=a_i\),贡献为 \(0\),所以我们可以得到:
\(ans(i>j)+ans(i<j)=ans\),而这两个偏序的答案是对偶的,所以我们可以得到 \(ans(i<j)=\dfrac{ans}{2}\),这样一来解决了偏序的限制。

\[2\times ans=\sum_{d=1}^{n}f(d)\sum_{i=1}^{n}\sum_{j=1}^{n}[\gcd(a_i,a_j)=d] \]

后面这个式子太常规了:

我们做一个变形,令 \(cnt[x]\) 表示 \(x\) 出现的次数,那么我们的:

\[\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(a_i,a_j)=d]\ 可以改为枚举\ a_i和\ a_j \ 为\ xd\ 的贡献 \]

\[=\sum_{t_1=1}^{n}\sum_{t_2=1}^{n}[\gcd(t_1,t_2)=d]\times cnt[t_1d]\times cnt[t_2d] \]

改为枚举值域的贡献,则后面的式子则与下标无关了,具体的考虑所有的值域匹对的 \(\gcd=d\) 的数量有多少个,这个我们预处理出桶就可以利用乘法原理计数了。上述式子实际就是枚举 \(a_i=d,2d,3d,...\) 的情况,因为只有这种情况才有可能构造出 \(\gcd(a_i,a_j)=d\),同时也可以根据这个反推出出 \(a_i=t_1d,a_2=t_2d\)

这个式子变形很常规:P2522

不会变的可以看看 oiwike

考虑 \(d\) 的约数:

\[=\sum_{t_1=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{t_2=1}^{\lfloor \frac{n}{d}\rfloor}[\gcd(t_1,t_2)=1]\times cnt[t_1d]\times cnt[t_2d] \]

这样变虽然枚举复杂度并没有多大变化,但我们可以用莫反了,莫反的常见性质:

\[[\gcd(i,j)=1]=\sum_{d\mid \gcd(i,j)}μ(d) \]

代入:

\[=\sum_{t_1=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{t_2=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{k\mid \gcd(t_1,t_2)}μ(k)\times cnt[t_1d]\times cnt[t_2d] \]

常规的套路,我们改为枚举 \(k\),因为 \(k \mid \gcd(t_1,t_2)\),所以我们直接考虑 \(k\) 的倍数贡献,即 \(\gcd(t_1,t_2)=k,2k,3k....\dfrac{n}{d}k\) 的贡献。(\(k\) 为公因数,所以应该有 \(k \le t_1,t_2\le \lfloor \frac{n}{d}\rfloor\)),即先枚举 \(k\),再枚举 \(k,2k,3k,....\) 的数量为 \(t_1\) 的数量贡献,继续枚举 \(t_2\) 中分别为 \(k,2k,3k....\) 的数量贡献,而最多的 \(i,j\le \lfloor\frac{n}{dk} \rfloor,使得\ ik,jk \le \lfloor\frac{n}{d} \rfloor\)。即 \(t_1=ik,t_2=jk\)

\[=\sum_{k=1}^{\lfloor\frac{n}{d} \rfloor}μ(k)\sum_{i=1}^{\lfloor\frac{n}{dk} \rfloor}\sum_{j=1}^{\lfloor\frac{n}{dk} \rfloor}cnt[ikd]\times cnt[jkd] \]

这玩意后半部分的 \(t_1,t_2\) 属于对偶部分,是完全一样的,可以考虑整理成:

\[\sum_{i=1}^{\lfloor\frac{n}{dk} \rfloor}\sum_{j=1}^{\lfloor\frac{n}{dk} \rfloor}cnt[ikd]\times cnt[jkd]=\sum_{i=1}^{\lfloor\frac{n}{dk} \rfloor}cnt[ikd]\sum_{j=1}^{\lfloor\frac{n}{dk} \rfloor}\times cnt[jkd] \]

\[=(\sum_{t=1}^{\lfloor \frac{n}{dk} \rfloor}cnt[tkd])^2,\ 这样一来就简化枚举复杂度了 \]

原式可以写成:

\[2\times ans=\sum_{d=1}^{n}f(d)\sum_{k=1}^{\lfloor \frac{n}{d}\rfloor} μ(k)(\sum_{t=1}^{\lfloor \frac{n}{dk} \rfloor}cnt[tkd])^2 \]

莫反的重要套路处理:

我们枚举 \(dk\) 来算贡献,令 $m=dk,d=\frac{m}{k} \(,\)k=\frac{m}{d} $则原式的三个部分分别考虑每个 \(dk\) 的贡献,先考虑换元后的式子:

\[\sum_{d=1}^{n}f(d)\sum_{k=1}^{\lfloor \frac{n}{d}\rfloor} μ(k)(\sum_{t=1}^{\lfloor \frac{n}{m} \rfloor}cnt[tm])^2 \]

容易看出 \(m \le n\),继续变换,考虑枚举 \(m\)

\[\sum_{m=1}^{n}\sum_{d=1}^{n}\sum_{k=1}^{\lfloor \frac{n}{d}\rfloor} ([dk=m]\times f(d)μ(k))(\sum_{t=1}^{\lfloor \frac{n}{m} \rfloor}cnt[tm])^2 \]

容易看出只有 \(d\mid m,k \mid m\) 才有贡献,先把 \(d\) 的范围缩小:

\[\sum_{m=1}^{n}\sum_{d \mid m}\sum_{k=1}^{\lfloor \frac{n}{d}\rfloor} ([dk=m]\times f(d)μ(k))(\sum_{t=1}^{\lfloor \frac{n}{m} \rfloor}cnt[tm])^2 \]

已知 \(d\),那么 \(k=\frac{m}{d}\),所以对于一个特定 \(d\),只有一个 \(k\) 可以和它满足 \(dk=m\),不需要枚举 \(k\) 了,同时我们还可以去掉 \([dk=m]\) 的限制了。

\[\sum_{m=1}^{n}\sum_{d \mid m} f(d)μ(k)(\sum_{t=1}^{\lfloor \frac{n}{m} \rfloor}cnt[tm])^2 \]

此时再将 \(k\) 换成 \(\frac{m}{d}\),可得:

\[2\times ans=\sum_{m=1}^{n}[(\sum_{d \mid m} f(d)μ(\frac{m}{d}))\times (\sum_{t=1}^{\lfloor \frac{n}{m} \rfloor}cnt[tm])^2] \]

观察每部分复杂度,预处理 \(f(x)\) 和 $μ(x) $,那么乘号前面式子就可以枚举因数算了,上文提到了这是对数级别。乘号后面式子枚举 \(m\)\(n\),内层枚举为 \(\frac{n}{1},\frac{n}{2},\frac{n}{3},...\) 显然这玩意是调和级数,复杂度得证:\(O(n\ln{n})\)

大常数核心代码:

constexpr int N = 1e6 + 10;
constexpr int MX = 1e6;
int mu[N], f[N];
bool vis[N];
vector<int> pos[N]; //因数桶
int cnt[N];inline void init()
{mu[1] = 1;vector<int> prim;forn(i, 2, MX){if (!vis[i]) prim.push_back(i), mu[i] = -1;for (const ll j : prim){if (i * j > MX) break;vis[i * j] = true;if (i % j == 0) break;mu[i * j] = -mu[i];}}forn(i, 1, MX) for (int j = i; j <= MX; j += i) pos[j].push_back(i);
}int n, a[N];inline void solve()
{cin >> n;forn(i, 1, n) cin >> a[i], cnt[a[i]]++;forn(i, 1, n){bool vis = true; //是否不存在i的因子,不存在为1,存在为0for (const int j : pos[i]){if (cnt[j]){vis = false;break;}}f[i] = vis;}ll ans = 0;forn(m, 1, n){ll pre = 0;for (const int d : pos[m]) pre += f[d] * mu[m / d];ll sum = 0;forn(t, 1, n/m) sum += cnt[t * m];const ll suf = sum * sum;ans += pre * suf;}cout << ans / 2 << endl;//clearforn(i, 1, n) f[i] = 0, cnt[a[i]]--;
}signed int main()
{// MyFileSpider//------------------------------------------------------// clock_t start = clock();init();int test = 1;//    read(test);cin >> test;forn(i, 1, test) solve();//    while (cin >> n, n)solve();//    while (cin >> test)solve();// clock_t end = clock();// cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

我们预处理出所有的因数桶和莫比乌斯函数,然后再进行上述的直接枚举因数求和。

小常数核心代码:

我们观察枚举 \(m\) 的时候如果能事先预处理出前面部分的乘积和后面部分的乘积就可以减小常数。

考虑前面部分在算的时候涉及到了枚举 \(d \mid m\),直接枚举 \(m\) 那得预处理 \(d\) 出来,我们考虑枚举 \(d\) 去算对每个 \(m\) 的贡献,这个很简单,类似调和级数的 \(+i\) 枚举即可。同时我们还可以根据枚举它的倍数的同时,如果当前的这个数是存在的,那么它的倍数显然对应的 \(f[m]\) 都应该为 \(0\),所以一次枚举因数即可求出 \(f\)\(pre\)

考虑后面部分,我们是需要枚举得到 \(im \le n\) 的所有的 \(cnt[im]\) 之和,它的贡献是针对于当前的 \(m\) 的。即我们枚举 \(m=d\),那么所有的 \(cnt[id]\) 之和即为 \(suf[d]\) 括号内贡献,然后取个平方即可。

constexpr int N = 1e6 + 10;
constexpr int MX = 1e6;
int mu[N], f[N];
bool vis[N];
int cnt[N];inline void init()
{mu[1] = 1;vector<int> prim;forn(i, 2, MX){if (!vis[i]) prim.push_back(i), mu[i] = -1;for (const ll j : prim){if (i * j > MX) break;vis[i * j] = true;if (i % j == 0) break;mu[i * j] = -mu[i];}}
}int n, a[N];
ll pre[N], suf[N];inline void solve()
{cin >> n;forn(i, 1, n) f[i] = 1, cin >> a[i], cnt[a[i]]++;ll ans = 0;forn(d, 1, n){//处理出f[d],同时满足 d|m,算出pre[m]for (int m = d; m <= n; m += d){f[m] &= cnt[d] == 0;pre[m] += f[d] * mu[m / d];suf[d] += cnt[m];}suf[d] *= suf[d];}forn(m, 1, n) ans += pre[m] * suf[m];cout << ans / 2 << endl;//clearforn(i, 1, n) f[i] = pre[i] = suf[i] = 0, cnt[a[i]]--;
}signed int main()
{// MyFileSpider//------------------------------------------------------// clock_t start = clock();init();int test = 1;//    read(test);cin >> test;forn(i, 1, test) solve();//    while (cin >> n, n)solve();//    while (cin >> test)solve();// clock_t end = clock();// cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/707712.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ITSM工作台:工程师效率与协同的新天地

在当今快节奏的IT运维领域,ITILDESK工作台脱颖而出,成为专为技术工程师量身打造的全能助手。这款平台不仅仅是一个工具集合体,它是一个精心设计的生态系统,旨在促进工程师的工作效率、团队协作与个人成长,为日常运维工作带来前所未有的便捷与智能。 一站式工作环境:从工…

HTML5中 drag 和 drop api

被拖放元素 -- A,目标元素 -- B。dragstart 事件主体是A,在开始拖放A时触发。 dragend 事件主体是A,在整个拖放操作结束时触发。 drag 事件主体是A,正在拖放A时触发(整个拖拽,drag事件会在被拖拉的节点上持续触发,相隔几百毫秒)。 dragenter 事件主体是B,在A进入某元素…

一些不错的地理题

2022年福建高考与土壤联系最密切的应该是植被 所以AB选项直接排除 然后的话30度的时候为什么大幅上升呢,主要是因为青藏高原 所以通过尺度也可以做这个题 10度的纬度,算是一个中等的尺度,而不应该是土壤植被这种小尺度大题意义类要去找主体、找问题 对你的意义 对我的意义 对…

项目管理之八大绩效域-------笔记(二)

八大绩效域详细解析 18.1 干系人绩效域跟干系人所有相关的活动.一、预期目标①与干系人建立高效的工作关系②干系人认同项目目标③支持项目的干系人提高了满意度,并从中收益④反对项目的干系人没有对项目产生负面影响三四是一个意思,就是支持你的人更支持你,反对你的人没有负面…

【日记】跟奇安信斗智斗勇,败下阵来(416 字)

正文今天一个客户都没有,让我快怀疑我们银行是不是要倒闭了……因为内外网 u 盘不知所踪,所以重新制了一个。深刻体会到了奇安信有多烂。有两个 u 盘,奇安信似乎把主控写坏了,插上电脑有反应,但是看不见盘符,磁盘管理也看不到。另一个也是这样,但后面莫名其妙好了。Wind…

DNS 的层级结构和分层结构是怎样的?

DNS(Domain Name System,域名系统)是互联网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。 DNS采用分层结构的原因主要有以下几点: 分层结构使得DNS系统能够轻松扩展,以容纳…

【吐槽】今天才发现PyCharm不支持对Python脚本进行块注释

在PyCharm中对Python脚本Ctrl + Shift + /进行块注释不起作用,然后使用OpenArk64查看是否热键占用冲突,没有发现其他占用。 然后发现PyCharm中Code菜单项下的选项Comment with Block Comment是灰色的。 又查了下,最后发现jetbrains官方帮助文档中已说明PyCharm不支持对Pytho…

three.js基础之mesh属性

mesh之位置、缩放、平移、旋转属性 <canvas id="mesh-properties"></canvas> <script type="importmap">{"imports": {"three": "./js/build/three.module.js","three/addons/": "./js/js…

Linux平台移植音频芯片实战记录

本文详细记录在NXP I.MX6ULL+Linux平台下进行WM8960音频芯片移植的过程,其他平台操作方法类似,希望为大家提供帮助。本文详细记录在NXP I.MX6ULL+Linux平台下进行WM8960音频芯片移植的过程,其他平台操作方法类似,希望为大家提供帮助。 1. 环境准备 平台: HD6ULL-IOT开发板 …

shared_ptr的概念和一些特性调查

shared_ptr 概念 shared_ptr 是 C++11 中引入的一种智能指针,用于自动管理资源,特别是动态分配的内存。它属于 头文件中定义的智能指针类之一,用于解决动态内存分配中的内存泄漏和资源生命周期管理问题。shared_ptr 通过引用计数机制来实现多个 shared_ptr 实例共享同一资源…

SIEM

背景和介绍 SIEM(Security Information and Event Management,简称SIEM)安全信息和事件管理,最初是被设计为一个工具,辅助企业实现合规和特定行业的规定。从时间维度上讲,SIEM是已经存活了近20年的技术。 其结合安全信息管理(security information management, SIM)和安…