Live2D

Solution -「JZOJ #5457」项链

Description

  Private link.

  给定一条有 n 个点的圆环和 m 中颜色, 求在位置旋转, 位置翻转, 颜色旋转等价的意义下, 本质不同的染色方案数.

  数据组数 T20, n,m1018.

Solution

  颜色旋转 ... 那么它就不是颜色了, 就用 Burnside 就行.

  首先需要明确的是, 我们的变换群长什么样子. 以 n=4,m=3 的情况为例:

G=({ι4,(4123),(3412),(2341)}{ι4,(2143),(3214),(4312)})×{ι3,(312),(231)}.

第一组是位置旋转, 第二组是位置对称翻转, 第三组是颜色旋转, × 是笛卡尔积.

  Burnside, 考虑不动点. 假设某个 (位置, 颜色) 的映射对为 (φp,φc), 位置 x 的颜色为 c(x), 那么不动点需要满足 φc(c(x))=c(φp(x)). 先来研究 φp 是旋转置换的情况. 通过一些形象的观察可以发现, 设 φp 的轮换大小为 l1, φc 的轮换大小为 l2, 则不动点存在当且仅当 l2l1. 此时对于 φp 的一个环, 只要确定其中一个位置的颜色, 就可以根据 φc(c(x))=c(φp(x)) 唯一确定环上其他点的颜色, 此时环的方案数为 m.

  因此, 枚举 l1,l2, 我们可以得到 φp 是旋转置换时的不动点总数 (式子里的 φ 是欧拉函数 w):

ans1=l1nφ(l1)l2m,l2l1φ(l2)mn/l1=l1nφ(l1)mn/l1gcd(m,l1).

  此后, 对于对称置换的 ans2, 需要讨论一下奇偶性:

  1. 2n, 此时所有置换的都有一个 l1=1 的轮换, 因此 l2=1. 总不动点数为 nm(n+1)/2.
  2. 2n,2m, 当对称轴放在两个点上时, 和前一种一样, 总不动点数为 n/2mn/2+1; 当对称轴放在两对点中间时, 所有 l2=2, 但是 2m, 所以还是有 l2=1, 总不动点数为 n/2mn/2.
  3. 2n,2m, 当对称轴放在两个点上时还是 n/2mn/2+1; 当对称轴放在两对点中间时, l2 可以取 12, 总不动点数为 n/2mn/2+n/2mn/2.

  置换群大小为 2nm, 所以答案为 (ans1+ans2)/(2nm). 计算上需要 Pollard-Rho, 精细处理 gcd, 顺便预处理 m 的光速幂, 可以做到 O(d(n)).

Code

/*+Rainybunny+*/

// #pragma GCC optimize("Ofast")

#include <bits/stdc++.h>

#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)

typedef long long LL;
typedef std::unordered_map<LL, int> MType;
typedef MType::iterator IType;
#define fi first
#define se second

const int MOD = 998244353;
int ans;
LL n, m;

namespace Factor {

inline LL add(LL u, const LL v, const LL m) {
    return (u += v) < m ? u : u - m;
}

inline LL mul(const LL u, const LL v, const LL m) {
    return __int128(u) * v % m;
}

inline LL mpow(LL u, LL v, LL m) {
    LL ret = 1;
    for (; v; u = mul(u, u, m), v >>= 1) ret = mul(ret, v & 1 ? u : 1, m);
    return ret;
}

inline LL gcd(const LL u, const LL v) { return v ? gcd(v, u % v) : u; }

inline LL labs(const LL u) { return u < 0 ? -u : u; }

inline bool millerRabin(const LL x, const LL b) {
    LL k = x - 1; while (!(k & 1)) k >>= 1;
    static LL pwr[70]; pwr[0] = 1, pwr[1] = mpow(b, k, x);
    while (k != x - 1) {
        pwr[pwr[0] + 1] = mul(pwr[pwr[0]], pwr[pwr[0]], x);
        ++pwr[0], k <<= 1;
    }
    per (i, pwr[0], 1) {
        if (pwr[i] != 1 && pwr[i] != x - 1) return false;
        if (pwr[i] == x - 1) return true;
    }
    return true;
}

inline bool isprime(const LL x) {
    if (x == 2 || x == 3 || x == 5 || x == 7 || x == 11) return true;
    if (x == 61 || x == 127) return true;
    if (!(x % 2) || !(x % 3) || !(x % 5) || !(x % 7) || !(x % 11))return false;
    if (!(x % 61) || !(x % 127)) return false;
    return millerRabin(x, 2) && millerRabin(x, 61) && millerRabin(x, 127);
}

inline LL pollardRho(const LL x) {
    static std::mt19937 emt(time(0) ^ 20120712);
    for (LL a = emt() % (x - 1) + 1, len = 1, st = 0, ed = 0; ;
      len <<= 1, st = ed) {
        LL prd = 1;
        rep (stp, 1, len) {
            prd = mul(prd, labs(st - (ed = add(mul(ed, ed, x), a, x))), x);
            if (!(stp & 127) && gcd(prd, x) > 1) return gcd(prd, x);
        }
        if (gcd(prd, x) > 1) return gcd(prd, x);
    }
}

inline void factor(LL x, MType& res, const int k = 1) {
    if (x == 1) return ;
    if (isprime(x)) return void(res[x] += k);
    LL d = pollardRho(x); int cnt = 0;
    while (!(x % d)) x /= d, ++cnt;
    factor(x, res, k), factor(d, res, k * cnt);
}

} // namespace Factor

inline int mul(const int u, const int v) { return 1ll * u * v % MOD; }
inline void subeq(int& u, const int v) { (u -= v) < 0 && (u += MOD); }
inline int sub(int u, const int v) { return (u -= v) < 0 ? u + MOD : u; }
inline void addeq(int& u, const int v) { (u += v) >= MOD && (u -= MOD); }
inline int add(int u, const int v) { return (u += v) < MOD ? u : u - MOD; }
inline int mpow(int u, int v) {
    int ret = 1;
    for (; v; u = mul(u, u), v >>= 1) ret = mul(ret, v & 1 ? u : 1);
    return ret;
}

MType buc;

inline int solve(const IType it, const int phi, const LL l) {
    if (it == buc.end()) {
        return mul(mul(phi, std::__gcd(m, l) % MOD),
          mpow(m % MOD, n / l % (MOD - 1)));
    }

    int ret = 0, tp = phi; LL tl = l;
    IType tmp(std::next(it));
    rep (i, 0, it->se) {
        addeq(ret, solve(tmp, tp, tl));
        tp = mul(tp, (it->fi - !i) % MOD), tl *= it->fi;
    }
    return ret;
}

int main() {
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%lld %lld", &n, &m), ans = 0;

        if (n & 1) {
            addeq(ans, mul(n % MOD, mpow(m % MOD, (n + 1 >> 1) % (MOD - 1))));
        } else {
            addeq(ans, mul((n >> 1) % MOD,
              mpow(m % MOD, (n >> 1) % (MOD - 1))));
            addeq(ans, mul((n >> 1) % MOD,
              mpow(m % MOD, (n / 2 + 1) % (MOD - 1))));
            if (~m & 1) {
                addeq(ans, mul((n >> 1) % MOD,
                  mpow(m % MOD, (n >> 1) % (MOD - 1))));
            }
        }

        buc.clear();
        Factor::factor(n, buc);
        addeq(ans, solve(buc.begin(), 1, 1));

        ans = mul(ans, mpow(mul(m % MOD, (n << 1) % MOD), MOD - 2));
        printf("%d\n", ans);
    }
    return 0;
}

posted @   Rainybunny  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示