首页 > 其他分享 >[luoguSP10707] Count on a tree II

[luoguSP10707] Count on a tree II

时间:2024-11-27 18:10:41浏览次数:4  
标签:Count eular rk1 int luoguSP10707 II ++ lca calc

题意

原题链接
给定一棵树,节点 \(i\) 上有颜色 \(c_i\),多次询问,每次查询两点之间的路径中的不同颜色数。

sol

这是一道类似普通莫队 [luoguSP3267] D-query 的题目,但是是在树上询问的,因此考虑将树转化为序列计算。将树转化为序列包括 DFS 序,欧拉序和树链剖分三种,树链剖分复杂度更大,而 DFS 序无法解决本题,因此只能使用欧拉序。容易发现,每次询问点 \(x,y\) 会有两种情况(下设 \(rk1_i\) 表示欧拉序中第一次出现 \(i\) 的位置,\(rk2_i\) 表示欧拉序中第二次出现 \(i\) 的位置,且 \(rk1_x < rk1_y\)):

  1. \(\operatorname{lca}(x,y) = x\),此时路径即为从 \(x \to y\),也就是区间 \([rk1_x,rk1_y]\);
  2. \(\operatorname{lca}(x,y) \ne x\),此时路径为 \(x \to \operatorname{lca}(x,y) \to y\)。考虑到可能中间会遍历到其他的子树,因此这段路径对应区间 \([rk2_x,rk1_y]\),去除出现两次的点并加上 \(\operatorname{lca}(x, y)\)(因为 \(\operatorname{lca}(x,y)\) 一定会在 \(rk2_y\) 后出现)。

这样,就将树上问题转化为了序列上的问题,按照普通莫队的方法做即可。
有一个 trick,可以记录一个 \(st\) 数组表示遍历次数,如果第奇数次遍历则添加该点,否则删除该点,这样就将添加、删除、去双三个操作合并为了一个。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_map>
#include <cmath>

using namespace std;

const int N = 40005, M = 100005;

int h[N], e[M], ne[M], idx;
int c[N];
int eular[N * 2], rk1[N], rk2[N], timestamp;
int fa[N][16], dep[N];
int block[N], ans[M], res;
unordered_map<int, int> buc;
bool st[N];
int n, m;

struct Ask {
    int l, r, lca, id;

    bool operator< (const Ask &W) const {
        if (block[l] != block[W.l]) return block[l] < block[W.l];
        if (block[l] & 1) return r < W.r;
        else return r > W.r;
    }
} queries[M];

void add(int a, int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs_init(int u, int father){
    dep[u] = dep[father] + 1, fa[u][0] = father;
    eular[ ++ timestamp] = u, rk1[u] = timestamp;

    for (int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if (j == father) continue;
        dfs_init(j, u);
    }

    eular[ ++ timestamp] = u, rk2[u] = timestamp;
}

int lca(int x, int y){
    if (dep[x] < dep[y]) swap(x, y);

    for (int i = 15; i >= 0; i -- ) {
        int fax = fa[x][i];
        if (dep[fax] >= dep[y]) x = fax;
    }

    if (x == y) return x;

    for (int i = 15; i >= 0; i -- ){
        int fax = fa[x][i], fay = fa[y][i];
        if (fax != fay) x = fax, y = fay;
    }

    return fa[x][0];
}

void calc(int x){
    st[x] = !st[x];
    if (st[x]) {
        if (!buc[c[x]]) res ++ ;
        buc[c[x]] ++ ;
    } else {
        buc[c[x]] -- ;
        if (!buc[c[x]]) res -- ;
    }
}

int main(){
    memset(h, -1, sizeof h);
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &c[i]);
    for (int i = 1; i < n; i ++ ) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }

    dfs_init(1, 0);
    for (int k = 1; k <= 15; k ++ ) 
        for (int i = 1; i <= n; i ++ )
            fa[i][k] = fa[fa[i][k - 1]][k - 1];

    for (int i = 1; i <= m; i ++ ){
        int x, y;
        scanf("%d%d", &x, &y);
        if (rk1[x] > rk1[y]) swap(x, y);
        int l = lca(x, y);
        if (x == l) queries[i] = {rk1[x], rk1[y], 0, i};
        else queries[i] = {rk2[x], rk1[y], l, i};
    }

    int Sz = sqrt(n * 2);
    int bcnt = ceil(2.0 * n / Sz);
    for (int i = 1; i <= bcnt; i ++ )
        for (int j = (i - 1) * Sz + 1; j <= min(i * Sz, n * 2); j ++ )
            block[j] = i; 

    sort(queries + 1, queries + m + 1);

    int l = 1, r = 0;
    for (int i = 1; i <= m; i ++ ){
        Ask &q = queries[i];
        while (l > q.l) calc(eular[ -- l]);
        while (r < q.r) calc(eular[ ++ r]);
        while (l < q.l) calc(eular[l ++ ]);
        while (r > q.r) calc(eular[r -- ]);
        if (q.lca) calc(q.lca);
        ans[q.id] = res;
        if (q.lca) calc(q.lca);
    }

    for (int i = 1; i <= m; i ++ ) printf("%d\n", ans[i]);
}

蒟蒻犯的若至错误

  • 写莫队的时候 while 写成 if 了 awa。

标签:Count,eular,rk1,int,luoguSP10707,II,++,lca,calc
From: https://www.cnblogs.com/XiaoJuRuoUP/p/-/luoguSP10707

相关文章

  • 2024年11月27日 比较字符的ASCII码值
    浅浅的随笔`#define_CRT_SECURE_NO_WARNINGS1//创建一个字符chara[10]//键盘输入十个字母并输出//找出ASCII最大值和最小值的字母//对数组进行排序(降序)#include<stdio.h>#defineN10intmain(){ chararr[N]={0};//创建数组 inti=0; printf("输入10个字......
  • 代码随想录:四数相加 II
    代码随想录:四数相加II我还以为会有更快的速度呢。。没想到最佳答案就是n^2不过值得一提的,这题一开始可能会想到用multiset来解决重复出现的元素,但实际上,multiset的查询速度是logn,是不如用哈希表的,所以用unordered_map,用键值对的值来表示元素出现的次数。classSolution{publ......
  • LeetCode【0227】基本计算器 II
    本文目录1中文题目2Python求解2.1求解思路2.2涉及方法2.3求解示例2.4Python代码2.5复杂度分析3题目总结1中文题目给定一个字符串表达式s,请实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分。可以假设给定的表达式总是有效的。所有中间结......
  • 代码随想录算法训练营day55 day57| 108.冗余连接 109.冗余连接II 53.寻宝
    学习资料:https://www.programmercarl.com/kamacoder/0108.冗余连接.html#思路图论并查集prim算法kruskal算法学习记录:108.冗余连接点击查看代码#并查集解法classUnionFind:def__init__(self,size):self.parent=list(range(size+1))deffind(se......
  • Java项目实战II基于SPringBoot的玩具销售商城管理系统(开发文档+数据库+源码)
    目录一、前言二、技术介绍三、系统实现四、核心代码五、源码获取全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末一、前言随着儿童娱乐与教育需求的日益增长,玩具市场呈现出蓬勃......
  • 深入了解 Python 的 Counter:一个强大的计数工具
    深入了解Python的Counter:一个强大的计数工具在Python中,Counter是collections模块中的一个子类,用于快速计数,是处理频率统计的利器。它看起来像字典,但功能远不止于此。什么是Counter?Counter是字典的一个扩展,它的设计目标是计数:键(key):要计数的元素。值(value):该元素......
  • Lake Counting S
    #include<iostream>#include<algorithm>#include<cstring>usingnamespacestd;constintN=100;intn,m;charg[N][N];boolst[N][N];//存状态:淹过or没淹过intres=0;intdx[]={1,1,1,0,0,-1,-1,-1};//八个方向intdy[]={-1,0,1,1,-1,1,0,-1};......
  • Java-CountDownLatch的用法
    一、使用场景        CountDownLatch也属于JUC。线程可以使用await()进行等待,多线程进行递减计数,等到计数到0的时候等待即不再阻塞,从而向下执行。在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总......
  • .NET 8.0 网站部署到IIS教程
    默认打开显示是这样。1.安装.NETHostingBundlehttps://dotnet.microsoft.com/zh-cn/download/dotnet/9.0 2.设置权限确保IIS的用户账户有权限访问发布文件夹。右键发布文件夹>属性>安全。添加用户IIS_IUSRS,并赋予读取与执行权限。    ......
  • 代码随想录算法训练营第二十五天|LeetCode491.递增子序列、46.全排列、47.全排列II、3
    前言打卡代码随想录算法训练营第49期第二十五天  ○(^皿^)っHiahiahia…首先十分推荐学算法的同学可以先了解一下代码随想录,可以在B站卡哥B站账号、代码随想录官方网站代码随想录了解,卡哥清晰易懂的算法教学让我直接果断关注,也十分有缘和第49期的训练营大家庭一起进步。今......