• SP10707 COT2


    题目可在vj上提交:https://vjudge.net/problem/SPOJ-COT2

    题意翻译

    • 给定 n 个结点的树,每个结点有一种颜色。
    • m 次询问,每次询问给出 u,v,回答 u,v 之间的路径上的结点的不同颜色数。
    • 1<=n<=4e4, 1<=m<=1e5

    输入输出样例

    输入 #1
    8 2
    105 2 9 3 8 5 7 7
    1 2
    1 3
    1 4
    3 5
    3 6
    3 7
    4 8
    2 5
    7 8
    输出 #1
    4
    4

     

    题解:

    树上莫队需要用到欧拉序

    欧拉序就是第一次遇见x结点的时候把它放到数组里面,最后x子节点遍历完之后再把x放入数组

    void dfs(int x)
    {
        ord[++len]=x;  //第一次遇见的时候放入数组
        first[x]=len;  //first和second数组是记录某个数在欧拉序中第一次出现和第二次出现的位置
        for(int i=head[x];i;i=e[i].next)
        {
            int to=e[i].to;
            if(to==fa[x][0]) continue;
            depth[to]=depth[x]+1;  //记录节点深度,深度从1开始
            fa[to][0]=x;
            for(int j=1;(1<<j)<=depth[to];++j)  //这一部分是为了求lca做的处理
            {
                fa[to][j]=fa[fa[to][j-1]][j-1];
            }
            dfs(to);
        }
        ord[++len]=x;  //子节点遍历完之后再放入数组
        second[x]=len;
    }

    比如你需要找1->7这条路径上的所有点,就可以通过欧拉序区间[first[1],first[7]]这个区间内的值就对应的1->7这个路径

    但是有些路径不是,看下图:

    对于1->10你会发现,这个区间内包含了很多实际上用不到的数。

    其实我们只需要把这个区间内出现两次的数删掉,剩下的就是1->10这个路径上遇到的点。

    1 2 4 7 7 4 5 5 2 3 6 8 9 9 10这个序列删除4、7、5、2、9就变成了1,3,6,8,10正好就是原路经

    至于为什么,你可以想一想欧拉序是怎么构成的,如果一个数出现了两次,那就证明这个数是1->10路径上的分支

    但是对于路径2->6,我们使用上面的方法你会发现获得的序列不满足我们的实际需求,正确操作是找到欧拉序的区间[last[2],start[6]]

    这一部分区间对应欧拉序为:2,3,6。少一个1,为什么少一个1?

    因为1是它们的最近父节点,你找的2,6分别在1的两个分支上,所以欧拉序这个区间内肯定不包含1。那我们只需要加上2和6的最近父节点就可以了

    这个找最近父节点可以使用lca,不会的可以看一下:lca讲解 && 例题 HDU - 4547 

    我们这里使用lca的第三种方式,使用倍增lca

    这样分析之后,我们就发现树上莫队就和普通莫队差不多了

    代码:

    #include <map>
    #include <set>
    #include <list>
    #include <queue>
    #include <deque>
    #include <cmath>
    #include <stack>
    #include <vector>
    #include <bitset>
    #include <cstdio>
    #include <string>
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int maxn = 2e5+10;
    const int INF = 0x3f3f3f3f;
    const double PI = 3.1415926;
    const long long N = 1000006;
    const double eps = 1e-10;
    typedef long long ll;
    #define mem(A, B) memset(A, B, sizeof(A))
    #define lson rt<<1 , L, mid
    #define rson rt<<1|1 , mid + 1, R
    #define ls rt<<1
    #define rs rt<<1|1
    #define SIS std::ios::sync_with_stdiget_mod_new(z-x)o(false), cin.tie(0), cout.tie(0)
    #define pll pair<long long, long long>
    #define lowbit(abcd) (abcd & (-abcd))
    #define max(a, b) ((a > b) ? (a) : (b))
    #define min(a, b) ((a < b) ? (a) : (b))
    inline int read() {  //读取整数
        int res = 0;
        char c = getchar();
        while(!isdigit(c)) c = getchar();
        while(isdigit(c)) res = (res << 1) + (res << 3) + (c ^ 48), c = getchar();
        return res;
    }
    int arr[maxn],cnt[maxn],first[maxn],second[maxn],ans[maxn],belong[maxn];
    int cnte,inp[maxn],vis[maxn],sizes,new_size,len,now,n,m; //莫队相关
    int ord[maxn],val[maxn],head[maxn],depth[maxn],fa[maxn][30];
    //ord保存的是欧拉序
    struct edge{
        int to,next;
    }e[maxn];
    struct Node{
        int l,r,lca,id;
    }node[maxn];
    bool cmp(Node a,Node b)
    {
        return (belong[a.l]^belong[b.l])?(belong[a.l]<belong[b.l]):((belong[a.l]&1)?a.r<b.r:a.r>b.r);
    }
    void add_edge(int x,int y)
    {
        e[++cnte]=(edge){y,head[x]};
        head[x]=cnte;
        e[++cnte]=(edge){x,head[y]};
        head[y]=cnte;
    }
    void dfs(int x)
    {
        ord[++len]=x;  //第一次遇见的时候放入数组
        first[x]=len;  //first和second数组是记录某个数在欧拉序中第一次出现和第二次出现的位置
        for(int i=head[x];i;i=e[i].next)
        {
            int to=e[i].to;
            if(to==fa[x][0]) continue;
            depth[to]=depth[x]+1;  //记录节点深度,深度从1开始
            fa[to][0]=x;
            for(int j=1;(1<<j)<=depth[to];++j)  //这一部分是为了求lca做的处理
            {
                fa[to][j]=fa[fa[to][j-1]][j-1];
            }
            dfs(to);
        }
        ord[++len]=x;  //子节点遍历完之后再放入数组
        second[x]=len;
    }
    int get_lca(int u,int v) //使用倍增lca
    {
        if(depth[u] < depth[v])
            swap(u, v);
        for(int i = 20; i + 1; --i)
            if(depth[u] - (1 << i) >= depth[v])
                u = fa[u][i];
        if(u == v)
            return u;
        for(int i = 20; i + 1; --i)
            if(fa[u][i] != fa[v][i])
                u = fa[u][i], v = fa[v][i];
        return fa[u][0];
    }
    void work(int pos)  //因为欧拉序中一个数出现两次就要删除,所以使用vis数组来标记下
    {
        vis[pos] ? now-=!--cnt[val[pos]] : now += !cnt[val[pos]]++;
        vis[pos] ^= 1;
    }
    int main()
    {
        //scanf("%d%d",&n,&m);
        n=read();
        m=read();
        for(int i=1;i<=n;++i)
        {
            //scanf("%d",&val[i]);
            inp[i]=val[i]=read();
        }
        sort(inp+1,inp+1+n);
        int tot=unique(inp+1,inp+1+n)-inp-1;  //去重后有多少元素
        for(int i=1;i<=n;++i)
        {
            //对去重后的数组进行二分
            val[i]=lower_bound(inp+1,inp+1+tot,val[i])-inp;
            //printf("%d
    ",val[i]);
        }
        for(int i=1;i<n;++i)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            add_edge(x,y);
        }
        depth[1]=1;
        dfs(1);
        sizes=sqrt(len);
        new_size=ceil((double)len/sizes);
        for(int i=1;i<=new_size;++i)
        {
            for(int j=(i-1)*sizes+1;j<=i*sizes;++j)
            {
                belong[j]=i;
            }
        }
        for(int i=1;i<=m;++i)
        {
            int x,y,z;
            x=read();
            y=read();
            //scanf("%d%d",&x,&y);
            z=get_lca(x,y);
            if(first[x]>first[y]) swap(x,y);
            if(x==z)  //如果其中一个节点是它俩的最近父节点,那就采用第一种方法
            {
                node[i].l=first[x];
                node[i].r=first[y];
            } 
            else  //否则就要最后加一个父节点
            {
                node[i].l=second[x];
                node[i].r=first[y];
                node[i].lca=z;
            }
            node[i].id=i;
        }
        sort(node+1,node+1+m,cmp);
        int l=1,r=0;
        for(int i=1;i<=m;++i)
        {
            int start=node[i].l,last=node[i].r,lca=node[i].lca;
            while(l<start) work(ord[l++]);
            while(l>start) work(ord[--l]);
            while(r>last) work(ord[r--]);
            while(r<last) work(ord[++r]);
            if(lca) work(lca);
            ans[node[i].id]=now;
            if(lca) work(lca);
        }
        for(int i=1;i<=m;++i)
            printf("%d
    ",ans[i]);
        return 0;
    }
  • 相关阅读:
    sqli-libs(38-41(堆叠注入)关)
    Vue 短信验证码逻辑
    promise(二)
    js类型转换
    微信小程序的组件传值
    微信小程序首页跳转详情页
    微信小程序的事件绑定
    微信小程序简介
    webpack4入门教程十分钟上手指南
    Vue 全局注册组件和局部注册组件
  • 原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/13723207.html
Copyright © 2020-2023  润新知