ARC062 - F. Painting Graphs with AtCoDeer (Polya+点双联通分量)

似乎好久都没写博客了....赶快来补一篇

题意

给你一个 n 个点 , 没有重边和自环的图 .

m 条边 , 每条边可以染 1k 中的一种颜色 .

对于任意一个简单环 , 可以将它的边的颜色进行旋转任意位 .

询问本质不同的染色方案数个数 .

数据范围

1n50,1m100,1k100

题解

将边 (或者说是很多条边) 分为 3 种类型 :

  1. 不属于任何一个简单环 , 它的贡献为 k .

  2. 属于且仅属于一个简单环 (除了环上的边没边了) , 设环长为 n . 它的贡献就是

1ni=0n1kgcd(i,n)

这个就是类似于项链染色的方案数求解 , 原因见 此篇博客 .

  1. 属于多个环 (或者说是构成了的环 , 除了环上的边还有其他边) . 能够证明可以通过旋转来交换任意两条边的颜色 .
    于是本质不同当且仅当有一种颜色数量不同 , 那计算的话 , 就是利用隔板法 把 m 条边 分成 k 组的方案数 (每组不一定要有边)

    那么我们肖就加入多的 k1 个隔板 , 然后贡献很显然就是(n+k1k1)

这个全都可以利用 Tarjan 求点双联通分量 (求割点的方法) 来判断种类 , 并在其中计算 , 把所有贡献乘起来就是最后的答案了.

时间复杂度就是 O(n+m) 轻松通过此题.

代码

#include <bits/stdc++.h> #define For(i, l, r) for(int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar() ) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar() ) x = (x << 1) + (x << 3) + (ch ^ '0'); return x * fh; } void File() { #ifdef zjp_shadow freopen ("F.in", "r", stdin); freopen ("F.out", "w", stdout); #endif } const int N = 55, M = 205; typedef long long ll; const ll Mod = 1e9 + 7; ll fpm(ll x, int power) { ll res = 1; for (; power; power >>= 1, (x *= x) %= Mod) if (power & 1) (res *= x) %= Mod; return res; } ll fac[M], ifac[M]; void Init(int maxn) { fac[0] = ifac[0] = 1ll; For (i, 1, maxn) fac[i] = fac[i - 1] * i % Mod; ifac[maxn] = fpm(fac[maxn], Mod - 2); Fordown (i, maxn - 1, 1) ifac[i] = ifac[i + 1] * (i + 1) % Mod; } ll C(int m, int n) { if (m > n || n < 0 || m < 0) return 0ll; return fac[n] * ifac[m] % Mod * ifac[n - m] % Mod; } set <int> Point; int n, m, k; ll ans = 1ll; ll Polya(int n) { ll res = 0; For (i, 1, n) (res += fpm(k, __gcd(n, i))) %= Mod; return res * fpm(n, Mod - 2) % Mod; } ll Permu(int m) { return C(k - 1, m + k - 1); } vector<int> G[N]; int dfn[N], lowlink[N], sta[N], top; void Tarjan(int u, int fa) { static int clk = 0; dfn[u] = lowlink[u] = (++ clk); sta[++ top] = u; for (int v : G[u]) if (!dfn[v]) { Tarjan(v, u), chkmin(lowlink[u], lowlink[v]); if (lowlink[v] >= dfn[u]) { Point.clear(); int n = 0, m = 0, Last; do Point.insert(Last = sta[top --]), ++ n; while (Last != v); Point.insert(u), ++ n; for (int x : Point) for (int v : G[x]) if ((bool)Point.count(v)) ++ m; m >>= 1; if (m < n) (ans *= k) %= Mod; if (m == n) (ans *= Polya(n)) %= Mod; if (m > n) (ans *= Permu(m)) %= Mod; } } else chkmin(lowlink[u], dfn[v]); if (!fa) -- top; } int main () { File(); n = read(), m = read(); k = read(); Init(m + k + 5); For (i, 1, m) { int u = read(), v = read(); G[u].push_back(v); G[v].push_back(u); } For (i, 1, n) if (!dfn[i]) Tarjan(i, 0); printf ("%lld\n", ans); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/8997612.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(484)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示