首页 > 其他分享 >P8338 [AHOI2022] 排列

P8338 [AHOI2022] 排列

时间:2024-02-03 16:22:07浏览次数:30  
标签:排列 cur int AHOI2022 P8338 cir lcm mx MOD

建边:\(i \to p_i\),这样会形成若干个置换环,每次操作相当于每个点同时走一步。

记置换环的数量为 \(m\),从 \(1\) 到 \(m\) 编号,第 \(i\) 个置换环的大小是 \(s_i\),\(bel_i\) 为点 \(i\) 所属的置换环编号。

显然 \(f(i, j) = 0\) 的充要条件是 \(i, j\) 在同一置换环上,否则 \(f(i, j) = \operatorname{lcm}\{\{s_k | k \ne bel_i, bel_j\} + \{s_{bel_i} + s_{bel_j}\}\}\)。

暴力枚举两个合并的置换环统计答案的时间复杂度是 \(\mathcal O(m^3 \log n)\),考虑优化。

其实我们并不关心合并的是哪两个置换环,我们关心的仅是它们俩的 \(s\)。

令 \(S = \{s_i | 1 \le i \le m \}\),又 \(\sum\limits_{i = 1}^m s_i = n\),故 \(|S| \le \sqrt n\),暴力枚举 \(S\) 里的任意两个的时间复杂度是 \(\mathcal O(n)\) 的。

问题来到如何快速求 \(\rm lcm\),存每个质因数的前 \(3\) 大指数,然后动态维护就行,时间复杂度 \(\mathcal O(n \log n)\)。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

constexpr int N = 5e5 + 10, MOD = 1e9 + 7;

int n, m, a[N], cir[N], cnt[N];

namespace DSU {
    int fa[N], sz[N];
    void init(int n) {for (int i = 1; i <= n; i++) fa[i] = i, sz[i] = 1;}
    int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
    void merge(int x, int y) {
        int fx = find(x), fy = find(y);
        if (fx != fy) {
            if (sz[fx] < sz[fy]) swap(fx, fy);
            fa[fy] = fx, sz[fx] += sz[fy];
        }
    }
}

ll inv(ll base, int e = MOD - 2) {
    ll res = 1;
    while (e) {
        if (e & 1) res = res * base % MOD;
        base = base * base % MOD;
        e >>= 1;
    }
    return res;
}

int mx[N][3]; ll lcm = 1;
inline void fix(int mx[], int p) {
    if (p > mx[0]) lcm = lcm * (p / mx[0]) % MOD, mx[2] = mx[1], mx[1] = mx[0], mx[0] = p;
    else if (p > mx[1]) mx[2] = mx[1], mx[1] = p;
    else mx[2] = max(mx[2], p);
}
inline void siu(int mx[], int p) {
    if (p == mx[2]) mx[2] = 1;
    else if (p == mx[1]) mx[1] = mx[2], mx[2] = 1;
    else if (p == mx[0]) lcm = lcm * inv(mx[0]) % MOD * mx[1] % MOD, mx[0] = mx[1], mx[1] = mx[2], mx[2] = 1;
}
void add(int x) {
    for (int i = 2; i * i <= x; i++) if (x % i == 0) {
        int p = 1;
        while (x % i == 0) x /= i, p *= i;
        fix(mx[i], p);
    }
    if (x > 1) fix(mx[x], x);
}
void del(int x) {
    for (int i = 2; i * i <= x; i++) if (x % i == 0) {
        int p = 1;
        while (x % i == 0) x /= i, p *= i;
        siu(mx[i], p);
    }
    if (x > 1) siu(mx[x], x);
}

void solve() {
    cin >> n; DSU::init(n);
    for (int i = 1; i <= n; i++) cin >> a[i], DSU::merge(i, a[i]), mx[i][0] = mx[i][1] = mx[i][2] = 1;
    m = 0; memset(cnt, 0, sizeof(cnt)); lcm = 1;
    for (int i = 1; i <= n; i++) if (DSU::find(i) == i) {
        add(DSU::sz[i]);
        if(!cnt[DSU::sz[i]]++) cir[++m] = DSU::sz[i];
    }
    ll ans = 0;
    for (int i = 1; i <= m; i++) {
        del(cir[i]);
        if (cnt[cir[i]] > 1) {
            ll cur = lcm; int p2 = __builtin_ctz(cir[i]) + 1;
            if ((1 << p2) > mx[2][0]) cur <<= 1;
            ans = (ans + cur * (cir[i] * cnt[cir[i]]) % MOD * (cir[i] * (cnt[cir[i]] - 1))) % MOD;
        }
        for (int j = i + 1; j <= m; j++) {
            del(cir[j]);
            int now = cir[i] + cir[j]; ll cur = lcm;
            for (int k = 2; k * k <= now; k++) if (now % k == 0) {
                int p = 1;
                while (now % k == 0) now /= k, p *= k;
                if (p > mx[k][0]) cur = cur * (p / mx[k][0]) % MOD;
            }
            if (now > mx[now][0]) cur = cur * now % MOD;
            ans = (ans + 2 * cur * (cir[i] * cnt[cir[i]]) % MOD * (cir[j] * cnt[cir[j]])) % MOD;
            add(cir[j]);
        }
        add(cir[i]);
    }
    cout << ans << '\n';
}

int main() {
    ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
    int t; cin >> t;
    while (t--) solve();
    return 0;
}

标签:排列,cur,int,AHOI2022,P8338,cir,lcm,mx,MOD
From: https://www.cnblogs.com/chy12321/p/18004809

相关文章

  • 交替字符串排列
    defalternative_string_arrange(first_str:str,second_str:str)->str:"""返回两个字符串的交替排列。:paramfirst_str:第一个字符串:paramsecond_str:第二个字符串:return:交替排列后的字符串>>>alternative_string_arrange("ABCD&qu......
  • 优美的排列II
    667.BeautifulArrangementII(Medium)给定两个整数n和k,你需要实现一个数组,这个数组包含从1到n的n个不同整数,同时满足以下条件:①如果这个数组是[a1,a2,a3,...,an],那么数组[|a1-a2|,|a2-a3|,|a3-a4|,...,|an-1-an|]中应该有且仅有k个不同整......
  • 洛谷题单指南-暴力枚举-P1706 全排列问题
    原题链接:https://www.luogu.com.cn/problem/P1706题意解读:n个数全排列问题,本质上,给定n个空位,枚举每个能填入空位的数,依次填入,每个数只能填一次。解题思路:如何填入n个数呢,可以借助于递归,流程如下:dfs(填入第k个数){如果已经填满n个数输出结果返回......
  • 31. 下一个排列(中)
    目录题目题解:找规律题目例如,arr=[1,2,3],以下这些都可以视作arr的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1]。整数数组的下一个排列是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的下一个排列就是......
  • 杨老师的照相排列
    蓝书上的那个补全数组思想其实是当然这个问题完全可以拓展到状态压缩中间那一串是乘法的意思(然而我也不太清楚跟状态压缩有啥关系)状态压缩具体内容见状态压缩的专题......
  • 桶排列
    #include<iostream>#include"wanghuali.h"usingnamespacestd;intmain(intargc,char**argv){wanghualin;n.set();n.get();return0;}classwanghuali{private:intm=10;intlist[10000]={0};inta;......
  • 桶排列
    classwanghuali{private:intx;inta[1000]={0};intm=5;public:voidset(){for(inti=0;i<5;i++){std::cin>>x;a[x]++;}}voidge......
  • 代码随想录 day29 非递减子序列 全排列 全排列 II
    非递减子序列cpp就业还是太难了还是转java吧好歹这个对双非还友好一些尝试写java的第一天本题关键是理解非递减子序列判断条件需要额外一个数组记录当前元素是否在本树层使用过记录在这个数组就说明用过了全排列本题系统的演示了怎么写全排列和最基本的组合问题的......
  • 命令行窗口排列 https://share.weiyun.com/EykMqNix 密码:ydvrx5
    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788......
  • Rime-小狼毫输入法改横向排列
    Rime-小狼毫输入法常用设置官方Rime参考书:https://github.com/rime/home/wiki/RimeWithSchemata#定製指南官方Rime定制指南:https://github.com/rime/home/wiki/CustomizationGuide修改纵向/横向排列选词框%appdata%目录下\Roaming\Rime\weasel.custom.yaml在patch:下添加"......