CF1884D Counting Rhyme 题解
题目链接:CF 或者 洛谷
给个莫反题解,讲讲常规套路
题目要求满足没有
那么考虑
那么我们考虑枚举
其中,
如果
这个算法的复杂度大概为:
直接求没办法变形,我们考虑莫反当中的变为枚举每个
对一些莫反不熟的朋友稍微介绍下变换思路,原先我们的思路是枚举
这个式子不会变形的可以做做:P2398。
常规变形,我们化简为
先将枚举
然后我们考虑解决
后面这个式子太常规了:
我们做一个变形,令
改为枚举值域的贡献,则后面的式子则与下标无关了,具体的考虑所有的值域匹对的
这个式子变形很常规:P2522
不会变的可以看看 oiwike
考虑
这样变虽然枚举复杂度并没有多大变化,但我们可以用莫反了,莫反的常见性质:
代入:
常规的套路,我们改为枚举
这玩意后半部分的
原式可以写成:
莫反的重要套路处理:
我们枚举
容易看出
容易看出只有
已知
此时再将
观察每部分复杂度,预处理
大常数核心代码:
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,存在为0 for (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; //clear forn(i, 1, n) f[i] = 0, cnt[a[i]]--; } signed int main() { // MyFile Spider //------------------------------------------------------ // 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; }
我们预处理出所有的因数桶和莫比乌斯函数,然后再进行上述的直接枚举因数求和。
小常数核心代码:
我们观察枚举
考虑前面部分在算的时候涉及到了枚举
考虑后面部分,我们是需要枚举得到
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; //clear forn(i, 1, n) f[i] = pre[i] = suf[i] = 0, cnt[a[i]]--; } signed int main() { // MyFile Spider //------------------------------------------------------ // 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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统