CCPC2020长春-K-并查集启发式合并map

2023大厂真题提交网址(含题解):

www.CodeFun2000.com(http://101.43.147.120/)

最近我们一直在将收集到的机试真题制作数据并搬运到自己的OJ上,供大家免费练习,体会真题难度。现在OJ已录入50+道2023年最新大厂真题,同时在不断的更新。同时,可以关注"塔子哥学算法"公众号获得每道题的题解。
在这里插入图片描述

题目大意:

直接题意:给你n个点,带点权 a i a_i ai。操作m次,模拟并查集的合并过程,每次操作之后询问每个集合中满足 g c d ( a i , a j ) = a i ⊕ a j gcd(a_i,a_j)=a_i \oplus a_j gcd(ai,aj)=aiaj的点对个数.还有对点权的修改.
n , m ≤ 1 e 5 n,m \leq 1e5 n,m1e5

题目思路:

其实这题很显然。让我们维护并查集内部的信息,无非就是线段树合并或者启发式合并set/map嘛.复杂度都是 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn),稍微说一下我分析 g c d ( a i , a j ) = a i ⊕ a j gcd(a_i,a_j)=a_i \oplus a_j gcd(ai,aj)=aiaj的思路.

观察式子发现异或实在没有什么规律,但是 g c d gcd gcd还是很有规律的。

我们知道,对于一个确定的 x x x。枚举他的 g c d gcd gcd不同的取值本质上就是枚举他的约数的个数。而枚举 1 到 n 1到n 1n的约数 就是一个调和级数的复杂度 O ( n l n n ) O(nlnn) O(nlnn)

对于 x x x,枚举其约数 d d d.那么上式 => d = x ⊕ r e s d =x \oplus res d=xres 推出 r e s = d ⊕ x res=d \oplus x res=dx.然后再判断 g c d ( r e s , x ) 是否等于 d gcd(res,x)是否等于d gcd(res,x)是否等于d.等于的话,那 r e s res res就是x的一个解,记录下来.

而且对于每个 g c d gcd gcd x x x最多就存在一个合法的值.所以这么做一定是对的。

然后map存集合的值。合并的时候暴力算贡献即可。具体看代码。

另外,对点权的修改:另开数组模拟即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
vector<int> fact[maxn * 2];
void init ()
{
    for (int i = 1 ; i < maxn * 2 ; i++)
        for (int j = i; j < maxn * 2; j += i){
            if (i == j) continue;
            if (__gcd(i , j^i) == i) fact[j].push_back(j^i);
        }
}
int f[maxn * 3] , sz[maxn * 3];
ll res;
unordered_map <int,int> q[maxn * 3];
int getf (int x) {
    return x == f[x] ? x : f[x] = getf(f[x]);
}
void mer (int a , int b)
{
    int fa = getf(a) , fb = getf(b);
    if (fa == fb) return ;
    if (sz[fa] < sz[fb]){
        f[fa] = fb;
        for (auto & g : q[fa]){
            for (auto &k : fact[g.first]) {
                if (q[fb].find(k) != q[fb].end()){
                    res += 1ll * q[fb][k] * g.second;
                }
            }
        }
        for (auto & g : q[fa]){
            q[fb][g.first] += g.second;
        }
        q[fa].clear();
        sz[fb] += sz[fa];
    }
    else {
        f[fb] = fa;
        for (auto & g : q[fb]){
            for (auto &k : fact[g.first]) {
                if (q[fa].find(k) != q[fa].end()){
                    res += 1ll * q[fa][k] * g.second;
                }
            }
        }
        for (auto & g : q[fb]){
            q[fa][g.first] += g.second;
        }
        q[fb].clear();
        sz[fa] += sz[fb];
    }

}
int val[maxn * 3];
int main()
{
    init();
    int n , m; scanf("%d%d" , &n , &m);
    for (int i = 1 ; i <= n + m; i++) sz[i] = 1 , f[i] = i;
    for (int i = 1 ; i <= n ; i++){
        int x ; scanf("%d" , &x);
        val[i] = x;
        q[i][x]++;
    }

    for (int i = 1 ; i <= m ; i++){
        int op , a , b;scanf("%d%d%d" , &op , &a , &b);
        if (op == 1){
            f[a] = a;
            sz[a] = 1;
            q[a][b]++;
            val[a] = b;
        }
        else  if (op == 2){
            mer(a ,  b);
        }
        else {
            for (auto & g : fact[val[a]]){
                res -= q[getf(a)][g];
            }
            q[getf(a)][val[a]]--;
            q[getf(a)][b]++;
            val[a] = b;
            for (auto & g : fact[val[a]]){
                res += q[getf(a)][g];
            }
        }
        printf("%lld\n" , res);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值