CCPC长春站 2020部分题解

  • Strange Memory
  • 本题采用DSU On Tree + 拆位,这个题目采用启发式合并很容易想到,关键是如何加速计算贡献的过程,首先,遍历每一个点然后寻找前缀树中对应的点是必不可少的,但是这里,我们可以开一个数组A[X][Y][Z],前缀树中,设一个点的编号为ID,则数组表示在前缀树中所有权值为X的编号中第Y位为Z的数量,这样的话,我们只需要对遍历到的点进行二进制拆位,检查前缀树中0/1的数量便可以加速计算贡献的过程了
//#define LOCAL
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)

template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 2e6 + 5;
int n;
int c[N], siz[N], son[N], pp[N], vis[N];
ll sum;
int t[N][25][2];
vector <int> edge[N];
void dfs1 (int u, int fa)
{
    siz[u] = 1;
    for (auto v : edge[u])
    {
        if (v != fa)
        {
            dfs1 (v, u);
            siz[u] += siz[v];
            if (siz[v] > siz[son[u]])
                son[u] = v;
        }
    }
}
void add (int u , int k)
{
    for (int i = 0 ; i < 21 ; i ++)
            t[c[u]][i][(u >> i) & 1] += k;
}
void get (int u , int fa, int lca)
{
    int num = u;
    int temp = (c[lca] ^ c[u]);
    for (int i = 0 ; i < 21 ; i ++)
        sum += 1ll * t[temp][i][(1^((num>>i)&1))] * pp[i];
    for (auto v : edge[u])
        if (v != fa)
            get (v, u, lca);
}
void seet (int u , int fa, int x)
{
    add (u, x);
    for (auto v : edge[u])
        if (v != fa)
            seet (v, u, x);
}
void count (int u , int fa, int k)
{
    for (auto v : edge[u])
    {
        if (v != fa && !vis[v])
        {
            if (k == 1) get (v , u , u);
            seet (v , u ,k);
        }
    }
    add (u, k);
}
void dfs2 (int u , int fa, int opt)
{
    for (auto v : edge[u])
        if (v != fa && v != son[u])
            dfs2(v, u, 0);
    if (son[u])
        dfs2(son[u], u, 1), vis[son[u]] = 1;
    count (u, fa, 1);
    if (son[u]) vis[son[u]] = 0 ;
    if (!opt) count(u ,fa, -1);
}


int main()
{
    read (n);
    pp[0] = 1;
    for (int i = 1 ; i < 21 ; i ++)
        pp[i] = pp[i - 1] * 2;
    for (int i = 1 ; i <= n ; i ++)
        read (c[i]);
    for (int i = 1 ; i < n  ; i ++)
    {
        int u , v;
        read (u , v);
        edge[u].pb(v);
        edge[v].pb(u);
    }
    dfs1 (1, 0);
    dfs2 (1 , 0, 0);
    write (sum), LF;
}
  • K - Ragdoll
  • 本题,读完题之后基本就会有一个做题思路,只需要利用启发式并查集,然后统计答案即可,这个题目的难点在于如果寻找出 GCD(ai, aj) == ai^aj的数对,拿到这个数对之后,就只需要采用按秩合并并查集暴力计算即可。
    按秩合并并查集siz必须初始化为1,否则就不是启发式了,因为siz始终为0
//#define LOCAL
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)

template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 4e5 + 5;

int n , m;
ll sum = 0;
vector <int> ve[N];
int fa[N], siz[N];
int a[N];
vector <int> s[N];
map <ll,ll> ma[N];
int findroot (int x)
{
    if (fa[x] == x)
        return x;
    return fa[x] = findroot (fa[x]);
}
void merge (int fax, int fay)
{
    if (fax == fay) return ;
    if (siz[fax] > siz[fay])
        swap (fax, fay);
    for (int v : s[fax])
        for (int it : ve[a[v]])
            sum += ma[fay][it];
    for (int it : s[fax])
        s[fay].pb (it), ma[fay][a[it]] += 1;
    fa[fax] = fay;
    siz[fay] += siz[fax];
}
int main()
{
    for(int i=1;i<N;i++) {
        for(int j=i+i;j<N;j+=i) {
            if(gcd(i,i^j)==i) ve[j].pb(i^j);
        }
    }
    scanf ("%d %d",&n , &m);
    for (int i = 1; i <= n ; i++)
        scanf ("%d",&a[i]), fa[i] = i, s[i].pb(i), ma[i][a[i]] = 1, siz[i] = 1;
    while (m --)
    {
        int op , x, u;
        scanf ("%d %d %d",&op, &x, &u);
        if (op == 1)
        {
            fa[x] = x;
            a[x] = u;
            siz[x] = 1;
            s[x].pb(x);
            ma[x][a[x]] ++;
        }
        if (op == 2)
            merge (findroot(u), findroot(x));
        if (op == 3)
        {
            int fax = fa[x];
            ma[fax][a[x]] --;
            for (int it : ve[a[x]])
                sum -= ma[fax][it];
            a[x] = u;
            for (int it : ve[a[x]])
                sum += ma[fax][it];
            ma[fax][a[x]] ++;
        }
        printf("%lld\n",sum);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值