ABC236Ex
首先 1,3 条件很好满足,但是 2 有点麻烦,考虑容斥掉条件 2。
令全集 E={(i,j)|1≤i<j≤N,i∈ℤ,j∈ℤ}。
对于 S⊆E,令 f(S) 表示以下的值:
考虑一张有 N 个点的无向图,如果 (i,j)∈S,那么 i,j 之间有一条边。对于图里的每一个连通块,连通块里的点的值都应该相等,那么整张图的确定点值的方案数就是 f(S)。
那么由容斥得答案即为 ∑S⊆Ef(S)(−1)|S|。
但是边的数量是 N2 级别的,直接做显然不行。
对于一个点集 T,令 g(T)=⌊Mlcmi∈TDi⌋。令 h(n) 表示令 n 个点连通的边集的容斥系数的和。即对于 n×(n−1)2 条边的子集,若它能令 n 个点连通,则如果边集大小为奇数则将 h(n) 减去 1,否则加上 1。
g(T) 可以 O(2N) 预处理出来,接下来讲讲怎么求出 h。
首先 h(1)=1,这很显然,接着对于 n≥2,h(n)=−(n−1)h(n−1),证明:
考虑用总的减去不连通的容斥系数。显然,因为总的方案是 2n×(n−1)2,奇偶各占一半,相互抵消。同理,考虑对于节点 1 所在连通块,如果其大小不为 n−1,因为剩下的点可以随便连边,同样奇偶各占一半,相互抵消,所以只用考虑其大小为 n−1,那么枚举剩下一个孤立点是什么,即 h(n)=0−(n−1)h(n−1)=−(n−1)h(n−1)。
那么对于 ∑f(S)(−1)|S|,S 将原图分成了若干个连通块,假设每个连通块的点集分别为 T1,T2,⋯,Tk,那么即为 k∏i=1g(Tk)h(|Tk|)。
令 dpT 表示将点集 T 划分成若干个连通块的答案,那么最终答案就是 dp{1,2,⋯,n}。
假设固定住 T 中的某个节点 u,那么转移为 dpT=∑T′⊂T,u∈T′g(T′)h(|T′|)×dpT∖T′,初值为 dp∅=1。
时间复杂度 O(3n)。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 16, mod = 998244353;
int n; ll m;
ll d[N];
int f[1<<N], g[1<<N], h[N+5];
void add(int &a, int b) {
a += b;
if (a >= mod) a -= mod;
}
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] = -1ll * (i - 1) * h[i - 1] % mod;
if (h[i] < 0) h[i] += mod;
}
for (int S = 1; S < (1 << n); ++S) {
ll lcm = 1;
for (int i = 0; i < n; ++i) if (S >> i & 1) {
lcm /= __gcd(lcm, d[i]);
if (lcm > m / d[i]) {
lcm = m + 1;
break;
}
lcm = lcm * d[i];
}
g[S] = (m / lcm) % mod * h[__builtin_popcount(S)] % mod;
}
f[0] = 1;
for (int S = 1; S < (1 << n); ++S) {
int mn = __builtin_ctz(S);
for (int _S = S; _S > 0; _S = (_S - 1) & S) if (_S >> mn & 1)
add(f[S], 1ll * f[S ^ _S] * g[_S] % mod);
}
printf("%d", f[(1 << n) - 1]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话