LCM 序列
求有多少个长度为 \(n\) 的正整数序列,满足 \(m\) 个形如 \(\text{lcm}(A_{X_i},A_{Y_i})=C_i\) 的限制,\(C_i\) 给的是它的唯一分解。
不难发现 lcm 是对于每个质因子上的指数取 \(\max\),容易将限制转化成一张图,对每一组限制存在边权为 \(C_i\) 的无向边 \((X_i,Y_i)\)
容易发现,一个点的最大取值是它所有相连的边中的最小边权,我们记作 \(mx_i\),如果一条边的两个端点的 \(mx_i\) 都比它的边权小,那就无解。我们称一条边被一个点匹配当且仅当这个点是边的端点且点的取值与边的取值相同。我们把边看成另一部分点建二分图,点到边有边当且仅当这个点有机会匹配掉这条边,也就是它是端点且 mx 等于这条边的权值。这样图右边的点(即原图中的边)会往左边连 1 或 2 条边。如果只连出去一条边,那么它就一定会被这个点匹配,对方案数没有贡献,我们可以把它删掉。而匹配它的点被选中之后会把所有和它相连的点全部匹配,也就是这些点不再再图中构成限制,我们也可以将它们删掉。
我们称一个点在一个方案里被选到,当且仅当它在这个方案里匹配了与它相连的边,即它的值取到了 \(mx\),这个方案的贡献显然就是所有没选到的点的 \(mx\) 的乘积
这样原图会被分成若干个连通块,不难发现每个连通块内点的 \(mx\) 相同,每个块内的右部点都会向左边连出去两个点。我们要在左边的点中选一些点使得它们连出去的点能包含右边所有点。在二分图上不太好搞,我们再还原回去(考虑二分图右边的点实质上是原图中的边,我们直接将右部点所连出去的两个点两两连边)。现在要求每条边都至少有一个端点被选到,直接做只能考虑最朴素的状压dp,设 \(f[i][S]\) 表示只考虑前 \(i\) 个点,被选择的点集合为 \(S\) 是否合法。不合法就是 \(0\),否则就是这个方案的贡献。转移就考虑,如果 \(i\in S\),则 \(f[i][S]=f[i-1][S\oplus (1<<i)]\),否则考虑判断合法,合法当且仅当 \(f[i-1][S]\) 合法且 \(i\) 连出去的所有点均在 \(S\) 中,此时 \(f[i][S]=mx_i\times f[i-1][S]\)。
显然时间复杂度爆炸,考虑经典的折半状压。我们把点平均分成两部分,对两部分都算出 \(f\),考虑合并。两种方案能合并当且仅当两部分各自合法,且两部分间每条边都有一个端点被选择。我们枚举第一部分的一个合法的状态 \(S\),那两部分间势必会有一些边没有被 \(S\) 中的点匹配到,第二部分必须要选择一些点。那么第二部分中能匹配的所有方案里都一定包含一个子集,我们要求这些方案的总和,实际上就是一个高维前缀和的形式。
代码略阴间:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, mod = 1e9 + 7;
#define VIT vector<int>::iterator
inline int read() {
register int s = 0; register char ch = getchar();
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) s = (s << 1) + (s << 3) + (ch & 15), ch = getchar();
return s;
}
int vis[N];
inline int power(int a, int b) {
int t = 1, y = a, k = b;
while (k) {
if (k & 1) t = (1ll * t * y) % mod;
y = (1ll * y * y) % mod;
k >>= 1;
} return t;
}
inline int min_(int a, int b) {
return a < b ? a : b;
}
struct edge {
int head, to, nxt, val;
} ed[N << 1];
int en = 0;
inline void addedge(int from, int to, int val) {
ed[++en].to = to; ed[en].nxt = ed[from].head; ed[from].head = en; ed[en].val = val;
}
inline void OR(int *f, int n) {
for (int i = 2, j = 1; i <= n; i <<= 1, j <<= 1) {
for (int k = 0; k < n; k += i)
for (int l = 0; l < j; ++l) {
f[j + k + l] += f[k + l];
if (f[j + k + l] >= mod) f[j + k + l] -= mod;
}
}
}
vector<int> nxt[N];
int pre[N][3], cnt[N], n, m, k, mx[N], ans[N], stk[N], top = 0, id[N];
bool ch[N];
int sat[N], f[20][1 << 20], g[20][1 << 20], tmp[1 << 20];
int main() {
// freopen("lcm.in", "r", stdin);
// freopen("lcm.out", "w", stdout);
n = read(); m = read(); k = read(); int e = 0;
for (int i = 0; i <= n * k; ++i)
mx[i] = mod;
for (int i = 1, x, y; i <= m; ++i) {
x = read(); y = read(); vis[x] = vis[y] = 1;
for (int j = 1, c; j <= k; ++j) {
c = read();
addedge((x - 1) * k + j, (y - 1) * k + j, c);
addedge((y - 1) * k + j, (x - 1) * k + j, c);
mx[(x - 1) * k + j] = min_(mx[(x - 1) * k + j], c);
mx[(y - 1) * k + j] = min_(mx[(y - 1) * k + j], c);
}
} memset(cnt, 0, sizeof cnt);
e = en >> 1;
for (int i = 1; i <= n * k; ++i) {
for (int j = ed[i].head; j; j = ed[j].nxt) {
int v = ed[j].to;
if (mx[i] < ed[j].val && mx[v] < ed[j].val) {
puts("0"); return 0;
}
if (mx[i] == ed[j].val) {
nxt[i].push_back(j + 1 >> 1);
pre[j + 1 >> 1][++cnt[j + 1 >> 1]] = i;
}
}
}
for (int i = 1; i <= n; ++i) {
if (!vis[i]) {
puts("-1");
return 0;
}
}
//delete the vertixes that must be chosen
for (int i = 1; i <= n * k; ++i) {
for (VIT it = nxt[i].begin(); it != nxt[i].end(); ++it) {
if (cnt[*it] == 1 || pre[*it][1] == pre[*it][2]) {
ch[i] = 1;
break;
}
}
if (ch[i])
for (VIT it = nxt[i].begin(); it != nxt[i].end(); ++it)
sat[*it] = 1;
}
vector<int> tt;
for (int i = 1; i <= n * k; ++i) {
if (ch[i]) continue;
tt.clear();
for (VIT it = nxt[i].begin(); it != nxt[i].end(); ++it) {
if (!sat[*it]) tt.push_back(*it);
} nxt[i] = tt;
}
int ans = 1;
memset(vis, 0, sizeof vis); queue<int> q;
memset(sat, 0, sizeof sat);
for (int i = 0; i <= en && i <= n * k; ++i)
ed[i].head = ed[i].nxt = ed[i].to = ed[i].val = 0;
en = 0;
for (int i = 1; i <= n * k; ++i) {
if (ch[i] || vis[i]) continue;
top = 0; stk[++top] = i; id[i] = top; vis[i] = i;
q.push(i);
while (!q.empty()) {
int x = q.front(); q.pop();
for (VIT it = nxt[x].begin(); it != nxt[x].end(); ++it) {
int p = 1; if (pre[*it][p] == x) p = 2;
if (!vis[pre[*it][p]]) {
q.push(pre[*it][p]);
stk[++top] = pre[*it][p];
id[pre[*it][p]] = top;
vis[pre[*it][p]] = i;
}
}
} int mid = top >> 1;
for (int j = 1; j <= top; ++j)
for (VIT it = nxt[stk[j]].begin(); it != nxt[stk[j]].end(); ++it) {
int p = 1;
if (pre[*it][p] == stk[j]) ++p;
addedge(j, id[pre[*it][p]], mx[i]);
}
f[0][0] = 1;
for (int j = 1; j <= mid; ++j) {
for (int S = 0; S < (1 << j); ++S) {
if ((S >> (j - 1)) & 1) {
int T = S ^ (1 << j - 1);
if (f[j - 1][T]) f[j][S] = f[j - 1][T];
else f[j][S] = 0;
} else {
bool fl = 1;
for (int k = ed[j].head; k; k = ed[k].nxt)
if (ed[k].to < j && !((S >> (ed[k].to - 1)) & 1)) {
fl = 0; break;
}
f[j][S] = fl ? (1ll * f[j - 1][S] * mx[i]) % mod : 0;
}
}
}
g[0][0] = 1;
for (int j = mid + 1; j <= top; ++j) {
for (int S = 0; S < (1 << j - mid); ++S) {
if ((S >> (j - mid - 1)) & 1) {
int T = S ^ (1 << j - mid - 1);
if (g[j - mid - 1][T]) g[j - mid][S] = g[j - mid - 1][T];
else g[j - mid][S] = 0;
} else {
bool fl = 1;
for (int k = ed[j].head; k; k = ed[k].nxt)
if (ed[k].to > mid && ed[k].to < j && !((S >> (ed[k].to - mid - 1)) & 1)) {
fl = 0; break;
}
g[j - mid][S] = fl ? (1ll * g[j - mid - 1][S] * mx[i]) % mod : 0;
}
}
}
for (int S = 0; S < (1 << top - mid); ++S) tmp[(~S) & ((1 << top - mid) - 1)] = g[top - mid][S];
OR(tmp, (1 << top - mid));
for (int S = 0; S < (1 << top - mid); ++S) g[top - mid][S] = tmp[(~S) & ((1 << top - mid) - 1)];
int res = 0;
for (int S = 0; S < (1 << mid); ++S) {
if (!f[mid][S]) continue;
int T = 0;
for (int j = mid + 1; j <= top; ++j) {
for (int k = ed[j].head; k; k = ed[k].nxt) {
if (ed[k].to <= mid && !((S >> (ed[k].to - 1)) & 1)) {
T |= (1 << j - mid - 1);
break;
}
}
}
res += (1ll * f[mid][S] * g[top - mid][T]) % mod;
if (res >= mod) res -= mod;
} ans = (1ll * ans * res) % mod;
for (int j = 0; j <= en && j <= top; ++j) ed[j].head = ed[j].nxt = ed[j].to = ed[j].val = 0;
en = 0;
} printf("%d", ans);
return 0;
}