zoukankan      html  css  js  c++  java
  • 树上的颜色

    【问题描述】
    给出一棵有N个点的有根树,结点编号为1,2,3,……,N,根结点编号为1,编号为i的结点图上颜色Ci。现在有M个询问,每个询问要求求出以结点u为根的子树上涂有此种颜色的结点个数不小于k的颜色个数有多少。

    【输入格式】
    输入文件名为color.in。
    第一行包含两个正整数N和M。
    第二行包含N个正整数,C1,C2,…,CN。
    接下来的N-1行每行有两个正整数x和y,表示结点x和y有边相连。
    再接下来的M行每行有两个正整数u和k,表示一个询问。

    【输出格式】
    输出文件名为color.out。
    输出M行,每行一个非负整数,对应每个询问的答案。

    【输入输出样例1】
    color.in
    4 1
    1 2 3 4
    1 2
    2 3
    3 4
    1 1
    color.out
    4

    【输入输出样例2】
    color.in
    8 5
    1 2 2 3 3 2 3 3
    1 2
    1 5
    2 3
    2 4
    5 6
    5 7
    5 8
    1 2
    1 3
    1 4
    2 3
    5 3
    color.out
    2
    2
    1
    0
    1

    【数据规模与约定】
    对于10%的数据,N≤100,M≤100,Ci≤100;
    对于30%的数据,N≤500,M≤100;
    对于60%的数据,N≤2000,M≤100000;
    对于100%的数据,N≤100000,M≤100000,Ci≤100000。

    【题解】
    10分做法:
    直接暴力,O(N*N*M)

    30分做法:
    对颜色离散化后直接暴力。

    60分做法:
    离线处理询问,将结点相同的询问放在一起处理。对一个结点,暴力处理子树上所有点,用color[i]统计第i钟颜色的数量,用sum[i]统计颜色数量为i,i+1,i+2,……的颜色个数。设要加入一个点,其颜色为x,则sum[color[x]+1]增加1,sum的其他值不变,同时color[x]++即可。同理,删去一个点,sum[color[x]–]–即可。因此处理一棵子树只需O(n)的时间统计出color和sum数组,并且做完后清空color和sum数组也是O(n)的,而sum数组就是询问的答案。时间复杂度O(N²)

    100分做法:
    在60分做法的基础上,考虑处理点u的时候,假设其孩子结点为v1,v2,…,vk,按顺序先处理其孩子结点,每做完一个需要清空,发现vk做完后不需要清空,因此不妨将孩子结点的子树结点个数最多的放在最后处理,这样可以证明时间复杂度变为O(NlogN)。(假设时间为2NlogN,然后用数学归纳法)

    这已经是我第n次树上题爆0了,我还是应该总结一下经验教训,克服对树上题的恐惧感,加强对该类题目的训练,先试着打对暴力,再逐渐像正解靠拢。同时记住,树上题有两个很套路的部分:链式前向星的存储和从父亲节点搜儿子的DFS。
    存储:

    struct edge
    {
        int to,next;
    }a[MAX<<1];
    void Add(int u,int v)
    {
        a[++cnt]=(edge){v,head[u]};
        head[u]=cnt;
    }

    DFS:

    void dfs(ll u,ll fa)
    {
        for(ll i=head[u];i;i=a[i].next)
        {
            ll v=a[i].to;
            if(v==fa) continue;
            dfs(v,u);
            //f[u]=max(f[u],f[v]+a[i].w);
        }
        //for(ll i=head[u];i;i=a[i].next)
         //   if(fa!=a[i].to)
           //     ans+=(f[u]-f[a[i].to]-a[i].w);
    }

    据学长的说法,这道题有两种做法:平衡树(Splay)和树上莫队。我自己只学会了莫队。
    莫队:按我的理解,莫队就是一种分块(优雅的暴力)。他把一个区间分作sqrt(n)+1个块(当n为平方数时是sqrt(n))。再把分成的块以,左端点所在块的标号为第一优先级,右端点为第二优先级排序。然后询问的答案都由上一个询问更新而来。
    树上莫队:讲究的是把子树变为链,在先序遍历前提下,左端点为子树根节点,右端点为搜到的最后一个儿子。把他们存下来,再按普通莫队搞即可。

    #include<iostream>
    #include<cmath>
    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    #define fp(i,a,b) for(int i=a;i<=b;i++)
    #define fq(i,a,b) for(int i=a;i>=b;i--)
    #define il inline
    #define ll long long 
    using namespace std;
    const int MAX=100005;
    struct edge
    {
        int to,next;
    }a[MAX<<1];
    int head[MAX],cnt;
    struct query
    {
        int l,r,k,id;
    }q[MAX];
    int line[MAX],Begin[MAX],End[MAX],tot;//line是最终处理出来的数列 Begin End表示某一个节点对应的某个区间
    int n,m,gen,re[MAX];
    int col[MAX],num[MAX],sum[MAX];
    int ans[MAX];
    void Add(int u,int v)
    {
        a[++cnt]=(edge){v,head[u]};
        head[u]=cnt;
    }
    void dfs(int u,int fa)
    {
        Begin[u]=++tot;
        line[tot]=col[u];
        for (int e=head[u];e;e=a[e].next)
        {
            int v=a[e].to;
            if (v==fa) continue;
            dfs(v,u);
        }
        End[u]=tot;
    }
    bool cmp(query a,query b)
    {
        return (re[a.l]<re[b.l])||(re[a.l]==re[b.l]&&a.r<b.r);
    }
    il int gi()
    {
       int x=0;
       short int t=1;
       char ch=getchar();
      while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
      if(ch=='-') t=-1,ch=getchar();
      while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
      return x*t;
    }
    int main()
    {
        n=gi();m=gi();gen=sqrt(n);
        fp(i,1,n)
            col[i]=gi();
        fp(i,1,n)
            re[i]=(i-1)/gen+1;
        fp(i,1,n-1)
        {
            int x=gi(),y=gi();
            Add(x,y);Add(y,x);
        }
        dfs(1,0);
        fp(i,1,m)
        {
            int u=gi();
            q[i]=(query){Begin[u],End[u],gi(),i};
        }
        sort(q+1,q+1+m,cmp);
        int L=1,R=0;
        fp(i,1,m)//60分做法的暴力搞
        {
            while(q[i].l<L) L--,num[line[L]]++,sum[num[line[L]]]++;
            while(q[i].l>L) sum[num[line[L]]]--,num[line[L]]--,L++;
            while(q[i].r>R) R++,num[line[R]]++,sum[num[line[R]]]++;
            while(q[i].r<R) sum[num[line[R]]]--,num[line[R]]--,R--;
            ans[q[i].id]=sum[q[i].k];
        }
        fp(i,1,m)
            printf("%d
    ",ans[i]);
        return 0;
    }
    
  • 相关阅读:
    基于Netty实现高性能通信程序之传输协议编码与解码
    博客园停止文章更新,最新文章请访问 www.zhaoyafei.cn,多谢您的支持!
    再谈PHP错误与异常处理
    C语言之预处理
    【转】linux sort 命令详解
    GO语言之channel
    浅谈Yii-admin的权限控制
    【转】搞清FastCgi与PHP-fpm之间的关系
    网站添加第三方登陆(PHP版)
    【转】PHP的Trait 特性
  • 原文地址:https://www.cnblogs.com/yanshannan/p/7392300.html
Copyright © 2011-2022 走看看