[Codeforces Round #221 (Div. 1)][D. Tree and Queries]

题目链接:375D - Tree and Queries

题目大意:给你一个有n个点的树,每个点都有其对应的颜色,给出m次询问(v,k),问v的子树中有多少种颜色至少出现k次

题解:先对所有的询问进行分类,即对所有相同的v合并到一起,这样就能转为离线处理(更新每个点的状态时同时求出答案)

   开两个map<int,int>,cnt[i][j]表示 i 节点的子树中 j 颜色出现了多少次,f[i][j]则表示 i 节点的子树中至少出现 j 次的颜色个数。dfs的时候暴力枚举所有颜色合并能够得出正确答案,但这样做显然会TLE,因此需要用到启发式合并进行优化。即每次合并的时候是将小的集合并到大的集合中去,这样做的话就只需要枚举小集合里的元素,可以将合并的时间复杂度缩减到O(log n)的级别

#include<bits/stdc++.h>
using namespace std;
#define N 100001
int n,m,u,v,c[N],k[N],ans[N];
map<int,int>cnt[N],f[N];
vector<int>d[N],q[N];
void dfs(int cur,int pre)
{
    cnt[cur][c[cur]]++,f[cur][1]++;
    for(auto nxt:d[cur])if(nxt!=pre)
      {
      dfs(nxt,cur);
      if(cnt[nxt].size()>cnt[cur].size())
        cnt[cur].swap(cnt[nxt]),f[cur].swap(f[nxt]);
      for(auto x:cnt[nxt])
        {
        int y=cnt[cur][x.first];
        cnt[cur][x.first]+=x.second;
        for(int i=y+1;i<=y+x.second;i++)f[cur][i]++;
        }
      }
    for(auto i:q[cur])ans[i]=f[cur][k[i]];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
      scanf("%d",&c[i]);
    for(int i=2;i<=n;i++)
      {
      scanf("%d%d",&u,&v);
      d[u].push_back(v);
      d[v].push_back(u);
      }
    for(int i=1;i<=m;i++)
      scanf("%d%d",&v,&k[i]),q[v].push_back(i);
    dfs(1,0);
    for(int i=1;i<=m;i++)
      printf("%d\n",ans[i]);
}
View Code

 

posted @ 2018-09-02 12:45  DeaphetS  阅读(232)  评论(0编辑  收藏  举报