[省选联考 2020 A 卷] 作业题
复盘 \(\color{black}{\text{n}}\color{red}{\text{ealchen}}\) 神仙讲的题。
完蛋我最近好像和生成树杠上了。
Description
给定一个图 \(G\) ,要对它所有生成树 \(T\) 求这样的一个权值和:
答案对 \(998244353\) 取模。
\(n \leq 20,\ m \leq \frac{n (n - 1)}{2},\ w \leq 152501\)
Analysis
先反演掉烦人的 \(\gcd\) ,然后大概可以通过枚举 \(\gcd\) 然后去快速找单边的贡献,看起来很对/hanx
可能是目前最短的 Analysis 了。
Solution
该换枚举 \(\gcd\) ,可以直接用欧拉反演一步到位:
(这是那个狄利克雷卷积的形式)
所以现在来看目的已经很明确了,就是对于每种因数 \(d\) ,我们需要所有是 \(d\) 的倍数的边权,让后让他们组成生成树。
所以说我们只会求数量啊,边权和怎么办呢?
于是有这么一个 trick(算是吧),在 CF917D 里应该是出现过的,大概就是我们让基尔霍夫矩阵的数带上元 \(x\) ,也就是形如 \((w_e x + 1)\) ,我们(手玩手玩)就能发现这个 \(\prod\) 的一次项对应的正好是当前边权存在的次数,最终求和也就是总的边权和。
(具体是因为:对于一种树,能够生成,那么一次项就正好仅包含一条边的权值,对于每条边的元都如此,那么求合起来就是一整棵树的权值和)
具体实现也不需要多项式,应为仅保留一次项和常数项,所以一个 pair 解决一切。
剩下就是多项式的加减乘除,加减很淼,乘展开一下式子 \((ax + b)(cx + d)\) 就行了,接下来是除法:
即我们要求:假定 \(ux + v = \frac{1}{cx + d}\) ,即求 \((ax + b)(ux + v)\) 。
因为只保留前两项,即可以理解为多项式在 \(\text{mod}\ x ^ 2\) 的意义下。
所以就有了:
\(x\) 不可能是零(不然有屁用啊),所以稍微解一下:
然后你带回 \((ax + b)(ux + v)\) 就可以啦。
Code
Code
/*
*/
#include
using namespace std;
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout);
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair pii;
#define fi first
#define se second
#define mp std::make_pair
const int mod = 998244353;
template
inline int M(A x) {return x;}
template
inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;}
const int N = 32, K = 152505;
inline int ksm(int a, int b) {
int tm = 1;
for (; b; b >>= 1, a = M(a, a)) if (b & 1) tm = M(tm, a);
return tm;
}
inline pii operator + (const pii a, const pii b) {
pii p = mp(a.fi + b.fi, a.se + b.se);
(p.fi >= mod) && (p.fi -= mod);
(p.se >= mod) && (p.se -= mod);
return p;
}
inline pii operator - (const pii a, const pii b) {
pii p = mp(a.fi - b.fi, a.se - b.se);
(p.fi < 0) && (p.fi += mod);
(p.se < 0) && (p.se += mod);
return p;
}
inline pii operator * (const pii a, const pii b) {
pii p = mp(M(a.fi, b.fi), M(a.fi, b.se) + M(a.se, b.fi));
(p.se >= mod) && (p.se -= mod);
return p;
}
inline pii operator / (const pii a, const pii b) {
int in = ksm(b.fi, mod - 2);
pii p = mp(M(a.fi, in), M(a.se, b.fi) - M(a.fi, b.se));
(p.se < 0) && (p.se += mod); p.se = M(p.se, in, in);
return p;
}
int n, m;
pii a[N][N];
struct mdzz {int u, v, w;} p[N * N];
inline void getpii(int di) {
memset(a, 0, sizeof(a));
for (int i = 1; i <= m; ++i) {
if (p[i].w % di) continue;
int u = p[i].u, v = p[i].v;
a[u][u] = a[u][u] + mp(1, p[i].w); a[v][v] = a[v][v] + mp(1, p[i].w);
a[u][v] = a[u][v] - mp(1, p[i].w); a[v][u] = a[v][u] - mp(1, p[i].w);
}
}
inline int del(int n) {
pii w = mp(1, 0);
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
for (pii div; a[i][i].fi > 0; ) {
div = a[j][i] / a[i][i];
for (int k = i; k <= n; ++k) {
a[j][k] = a[j][k] - div * a[i][k];
}
swap(a[i], a[j]); w = mp(0, 0) - w;
}
swap(a[i], a[j]); w = mp(0, 0) - w;
}
}
for (int i = 1; i <= n; ++i) w = w * a[i][i];
return w.se;
}
int cn[K], phi[K], ans;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m;
int mx = 0;
for (int i = 1; i <= m; ++i) {
std::cin >> p[i].u >> p[i].v >> p[i].w;
mx = std::max(mx, p[i].w);
for (int j = 1; j * j <= p[i].w; ++j) {
if (p[i].w % j) continue;
++cn[j];
(j * j != p[i].w) && (++cn[p[i].w / j]);
}
}
phi[1] = 1;
for (int i = 2; i <= mx; ++i) {
if (phi[i]) continue;
phi[i] = i - 1;
for (int j = i << 1; j <= mx; j += i) {
if (!phi[j]) phi[j] = j;
phi[j] = phi[j] / i * (i - 1);
}
}
for (int i = 1; i <= mx; ++i) {
if (cn[i] < n - 1) continue;//无法作为gcd做出贡献
getpii(i);
ans += M(phi[i], del(n - 1));
(ans >= mod) && (ans -= mod);
}
std::cout << ans << "\n";
return 0;
}
深深明白自己的弱小。