[ABC236Ex] Distinct Multiples (集合划分容斥)
关键限制是
这也是上午模拟赛 导致我暴力不会的东西
考虑更一般的,连边 ,表示 的限制,那么本题考虑这样的一个完全图
那么枚举选哪些边,记为集合 ,于是答案就是
此时的计算就面临一些连通块,连通块内要求 相等,这是容易计算的
但容斥本身是 ,考虑优化
因为枚举选的边集,很多 是相同的,因为计算只取决于各个连通块的情况,而完全图一些边的差异不会影响各个连通块的连通关系,那么优化 的定义,转向只考虑点集, 表示将点集 划分成若干个连通的答案
那么 就是各个连通块方案数带容斥系数的和
考虑一个连通块的答案 ,一开始连通块的限制也是个完全图
那么继续枚举边集,要求选了的边集能使点集 连通
, 表示连通块 内要求 都相同的答案,这是容易计算的
记容斥系数和为 ,算 的话连通约束较困难,再容斥计算,记
不考虑连通,那么直接枚举选了几条边即可,可以看出是个二项式定理,那么有答案为
再减掉不连通的情况,此时 被分成若干连通块,为避免考虑连通块时算重,枚举代表元所在连通块大小
那么 ,得到
原来这是一个经典的容斥模型,即集合划分容斥。
本题 的转移是枚举子集的,但仍会算重,同样枚举代表元所在连通块
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 17, P = 998244353; int n, h[N], f[1 << N], g[1 << N], num[1 << N]; LL m, d[N]; void Add(int &x, int y){((x += y) >= P) && (x -= P);} int main() { scanf("%d%lld", &n, &m); for(int i = 0; i < n; i++) scanf("%lld", &d[i]); h[1] = 1; for(int i = 2; i <= n; i++) h[i] = (P - (LL)(i - 1) * h[i - 1] % P) % P; int all = (1 << n) - 1; for(int i = 1; i <= all; i++) { for(int j = i; j; j -= j & -j) ++num[i]; LL lcm = 1; for(int j = 0; j < n; j++) if (i >> j & 1) { lcm /= __gcd(lcm, d[j]); if (lcm > m / d[j]) {lcm = m + 1; break;} lcm *= d[j]; } g[i] = m / lcm % P; } f[0] = 1; for(int s = 1; s <= all; s++) { int now = __builtin_ctz(s); for(int t = s; t; t = (t - 1) & s) if (t >> now & 1) Add(f[s], (LL)g[t] * h[num[t]] % P * f[s ^ t] % P); } printf("%d\n", f[all]); }
标签:
动态规划_计数dp
, 动态规划_状态压缩dp
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具