最终的答案要求对于每一条边分别求出包含这条边的强连通生成子图的权值之和,那么容斥一下就是所有强连通生成子图的权值之和减去不包含这条边的强连通生成子图的权值之和。不包含这条边就是把这条边删掉,剩下的图中所有强连通生成子图的权值之和。因此假设没有重边,那么边 u\to v 的答案即 f(w_{1,2},\dots,w_{u,v},\dots,w_{n,n-1})-f(w_{1,2},\dots,0,\dots,w_{n,n-1})。
我们分析一下 f 的性质,可以发现:如果对于 f 的除了 w_{i,j} 以外的自变量全都不变,将 f 函数写成 f_{\dots,w_{i,j-1},w_{i,j+1},\dots}(w_{i,j}) 的形式,那么此时 f 函数为一次函数。这一点比较显然,因为我们考虑将图中的 i\to j 边权增加 1,那么答案的增量就是原图中所有包含 i\to j 的强连通分量,计算边权之积时不算入 w_{i,j} 贡献的权制之和。由于我们固定了除了 w_{i,j} 以外的其他所有边权,所以这个增量就是一个定值。那么此时 f 就可以写成一次函数的形式了。
考虑将 f 函数展开,那么由于 f 对每个变量都是一次函数,所以展开式中不可能存在关于 w_{i,j} 的二次项。如果我们称这样的不存在二次项的形式为一种线性组合,那么 f 函数值就是自变量 w_{1,2},\dots,w_{n,n-1} 的线性组合。而我们计算 f(w_{1,2},\dots,w_{n,n-1}) 时,不是直接由这些边权值计算的,而是通过许多中间变量,这些中间变量都是更早计算的变量的线性组合。
下面主要来说怎么倒着推增量。我们对于每一个变量,新开一个变量表示该位置增加 1 对答案的增量。考虑倒着做的时候,由于我们需要知道所有原变量在当前时刻的值,所以我们倒推的时候顺便把原变量做一遍逆操作作为时间倒流。下面逐一解释正着做时所有可能涉及到的操作以及如何倒着推增量。(下面的式子中如果 a 表示原变量,那么 A 就表示新开的记录增量的变量,且都在模意义下)
1. a += b
- $a'$ 增加 $1$ 时,$a$ 增加 $1$,答案增加 $A$;
- $b'$ 增加 $1$ 时,$a,b$ 增加 $1$,答案增加 $A+B$。
因此增量的变化为 $(A,A+B)\to(A,B)$。
最终反推时的操作就是 `a -= b, B += A`。
### 2. `a -= b * c`
$(a',b',c')\to(a'-b'c',b',c')$,$a'-b'c'=a$,$b'=b$,$c'=c$。
- $a'$ 增加 $1$ 时,$a$ 增加 $1$,答案增加 $A$;
- $b'$ 增加 $1$ 时,$a$ 增加 $-c'$,$b$ 增加 $1$,答案增加 $B-c'A$;
- $c'$ 增加 $1$ 时,$a$ 增加 $-b'$,$c$ 增加 $1$,答案增加 $C-b'A$。
因此增量的变化为 $(A,B-c'A,C-b'A)\to(A,B,C)$。
最终反推时的操作就是 `a += b * c, B -= A * c, C -= A * b`。
### 3. `a = b`
$(a',b')\to (b',b')$,$b'=a=b$。
- $a'$ 增加 $1$ 时,$a,b$ 均无变化,答案增加 $0$;
- $b'$ 增加 $1$ 时,$a,b$ 增加 $1$,答案增加 $A+B$。
这个操作没有逆操作,因为覆盖后我们不可能复原出 $a'$ 的值。但是,由于 $a'$ 一定会被覆盖,其变化对答案没有影响,因此在后面的反推中其具体值对答案也没有影响。所以这里 $a'$ 赋任意值即可。增量的变化为 $(0,A+B)\to(A,B)$。
最终反推时的操作就是 `a = 0, B += A, A = 0`。
### 4. `a = b * c`
$(a',b',c')\to(b'c',b',c')$,$b'c'=a$,$b'=b$,$c'=c$。
- $a'$ 增加 $1$ 时,$a,b,c$ 均无变化,答案增加 $0$;
- $b'$ 增加 $1$ 时,$a$ 增加 $c'$,$b$ 增加 $1$,答案增加 $B+c'A$;
- $c'$ 增加 $1$ 时,$a$ 增加 $b'$,$c$ 增加 $1$,答案增加 $C+b'A$。
同 3,这里 $a'$ 赋任意值即可。增量的变化为 $(0,B+c'A,C+b'A)\to(A,B,C)$。
最终反推时的操作就是 `a = 0, B += A * c, C += A * b, A = 0`。
### 5. `a = 1`
$(a')\to(1)$,$1=a$。
- $a'$ 增加 $1$ 时,$a$ 无变化,答案增加 $0$。
同3,这里 $a'$ 赋任意值即可。增量的变化为 $(0)\to(A)$。
最终反推时的操作就是 `a = A = 0`。
---
最后,反推的时间复杂度与正推相同,因此最终时间复杂度与主旋律的时间复杂度相同,可以做到 $O(3^n+n2^n)$。
:::info[代码]
```cpp
#include <bits/stdc++.h>
using namespace std;
//#define int long long
const int N = 20, M = (1 << 16), K = 1e5 + 5, mod = 998244353;
int n, m, u[K], v[K], w[K], lg[M];
int e[N][N], sum[N][M], val[M], num[M], f[M], dp[M];
int E[N][N], Sum[N][M], Val[M], Num[M], F[M], Dp[M];
int qpow(int x, int y) {
if (!y) return 1;
int now = qpow(x, y >> 1);
if (!(y & 1)) return 1ll * now * now % mod;
return 1ll * now * now % mod * x % mod;
}
int main() {
//freopen(".in", "r", stdin);
//freopen(".out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) e[i][j] = 1;
for (int i = 1; i <= m; i++) {
cin >> u[i] >> v[i] >> w[i];
e[u[i]][v[i]] = 1ll * e[u[i]][v[i]] * (w[i] + 1) % mod;
}
for (int i = 1; i <= n; i++) sum[i][0] = 1;
for (int i = 1; i < (1 << n); i++) lg[i] = lg[i >> 1] + 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j < (1 << n); j++) {
int now = j & (-j);
sum[i][j] = 1ll * sum[i][j ^
now] * e[lg[now]][i] % mod;
}
for (int i = 1; i < (1 << n); i++) {
val[0] = 1;
for (int j = (i - 1) & i; j; j = (j - 1) & i) {
int now = (i ^ j), cur = now & (-now);
val[now] = 1ll * val[now ^
cur] * sum[lg[cur]][i] % mod;
}
int cur = i & (-i);
val[i] = 1ll * val[i ^ cur] * sum[lg[cur]][i] % mod;
dp[i] = val[i];
for (int j = (i - 1) & i; j; j = (j - 1) & i) {
if ((i & (-i)) != (j & (-j))) continue;
f[i] += mod - 1ll * dp[j] * f[i ^ j] % mod;
if (f[i] >= mod) f[i] -= mod;
}
for (int j = i; j; j = (j - 1) & i) {
dp[i] += mod - 1ll * f[j] * val[i ^ j] % mod;
if (dp[i] >= mod) dp[i] -= mod;
}
f[i] = (f[i] + dp[i]) % mod;
}
Dp[(1 << n) - 1] = 1;
for (int i = (1 << n) - 1; i >= 1; i--) {
val[0] = 1;
for (int j = (i - 1) & i; j; j = (j - 1) & i) {
int now = (i ^ j), cur = now & (-now);
val[now] = 1ll * val[now ^
cur] * sum[lg[cur]][i] % mod;
}
int cur = i & (-i);
val[i] = 1ll * val[i ^ cur] * sum[lg[cur]][i] % mod;
f[i] = (f[i] - dp[i] + mod) % mod;
Dp[i] = (Dp[i] + F[i]) % mod;
for (int j = i; j; j = (j - 1) & i) {
dp[i] += 1ll * f[j] * val[i ^ j] % mod;
if (dp[i] >= mod) dp[i] -= mod;
F[j] += mod - 1ll * Dp[i] * val[i ^ j] % mod;
if (F[j] >= mod) F[j] -= mod;
Val[i ^ j] += mod - 1ll * Dp[i] * f[j] % mod;
if (Val[i ^ j] >= mod) Val[i ^ j] -= mod;
}
for (int j = (i - 1) & i; j; j = (j - 1) & i) {
if ((i & (-i)) != (j & (-j))) continue;
f[i] += 1ll * dp[j] * f[i ^ j] % mod;
if (f[i] >= mod) f[i] -= mod;
Dp[j] += mod - 1ll * F[i] * f[i ^ j] % mod;
if (Dp[j] >= mod) Dp[j] -= mod;
F[i ^ j] += mod - 1ll * F[i] * dp[j] % mod;
if (F[i ^ j] >= mod) F[i ^ j] -= mod;
}
Val[i] = (Val[i] + Dp[i]) % mod, Dp[i] = dp[i] = 0;
for (int j = i; j; j = (j - 1) & i) {
int cur = j & (-j);
Val[j ^ cur] += 1ll * Val[
j] * sum[lg[cur]][i] % mod;
if (Val[j ^ cur] >= mod) Val[j ^ cur] -= mod;
Sum[lg[cur]][i] += 1ll *
Val[j] * val[j ^ cur] % mod;
if (Sum[lg[cur]][i] >= mod) Sum[lg[cur]][i] -= mod;
Val[j] = val[j] = 0;
}
Val[0] = val[0] = 0;
}
for (int i = 1; i <= n; i++)
for (int j = (1 << n) - 1; j >= 1; j--) {
int now = j & (-j);
Sum[i][j ^ now] = (Sum[i][j ^ now] + 1ll *
Sum[i][j] * e[lg[now]][i] % mod) % mod;
E[lg[now]][i] = (E[lg[now]][i] + 1ll *
Sum[i][j] * sum[i][j ^ now] % mod) % mod;
sum[i][j] = Sum[i][j] = 0;
}
for (int i = 1; i <= m; i++) {
int now = (e[u[i]][v[i]] - 1ll * e[u[i]][v[i]] *
qpow(w[i] + 1, mod - 2) % mod + mod) % mod;
cout << 1ll * now * E[u[i]][v[i]] % mod << '\n';
}
return 0;
}
```
:::