牛客练习赛47——E(莫队或集合或树上启发式合并)

 

链接:https://ac.nowcoder.com/acm/contest/904/E
来源:牛客网
 

DongDong数颜色

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 524288K,其他语言1048576K
64bit IO Format: %lld

题目描述

DongDong是个喜欢数颜色的女孩子,她已经熟练地掌握了在序列上数颜色的操作,现在她开始学习如何在树上数颜色,现在给定一个n个点,n-1条边的树形图(视1号店为根),每个点有一个颜色,每次询问以x为根的子树中有多少种不同的颜色,DongDong轻松地解决了这个问题,但她想考考会编程的你。

输入描述:

第一行两个整数n,m

第二行n个整数,表示每个点的颜色

接下来n-1行每行u,v,表示存在一条从u到v的双向边(保证最终图形是树形图)

2<=n<=100000,1<=m,color<=n,

输出描述:

共m行:每行输出相应询问的答案

示例1

输入

复制

4 3
1 1 2 3
1 2
2 3
1 4
1
2
4

输出

复制

3
2
1

很经典的一个题,有好几种做法:

1.DFS序+莫队算法离线询问

我们知道,一个点的子树的DFS序是连续的,那么如果用inx[i]表示点的DFS序,out[i]表示它的子树最后一个点的DFS序,那么一个询问,就变成问inx[i]到out[i]这一个连续区间内有多少个不同的数,怎么样,是不是很明显的一个莫队?

之前接触莫队只限于理念,这里有机会好好学习一下:大神博客传送门

这个传送门博客讲了几种经典的莫队模板,修改时只需对del和add函数修改即可。

此题代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
using namespace std;

const int maxn=100000+10;
int inx[maxn];
int out[maxn];
int n,k;
int color[maxn];
int c[maxn];
vector<int>G[maxn];
int cnt;
int vis[maxn];
void dfs(int u){
    inx[u]=++cnt;
    vis[u]=1;
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(!vis[v])
            dfs(v);
    }
    out[u]=cnt;
}
//莫队离线询问
struct node{
    int l,r,id;
}Q[maxn];

int pos[maxn];//保存所在块
bool cmp(const node &a,const node &b)
{
    if(pos[a.l]==pos[b.l])
        return a.r<b.r;
    return pos[a.l]<pos[b.l];
}

int flag[maxn];//数字在当前区间出现次数
int Ans;
void add(int x)
{
    flag[c[x]]++;
    if(flag[c[x]]==1) Ans++;
}

void del(int x)
{
    flag[c[x]]--;
    if(flag[c[x]]==0) Ans--;
}
int ans[maxn];
int main(){
    scanf("%d%d",&n,&k);
    cnt=0;
    int sz=sqrt(n);
    for(int i=1;i<=n;i++){
        scanf("%d",&color[i]);
        pos[i]=i/sz;
    }
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    memset(vis,0,sizeof(vis));
    memset(out,0,sizeof(out));
    memset(inx,0,sizeof(inx));
    memset(flag,0,sizeof(flag));
    dfs(1);
    for(int i=1;i<=n;i++){
        c[inx[i]]=color[i];
    }
    Ans=0;
    for(int i=1;i<=k;i++){
        int u;
        scanf("%d",&u);
        int ll=inx[u],rr=out[u];
        Q[i].l=ll;
        Q[i].r=rr;
        Q[i].id=i;
    }
    sort(Q+1,Q+k+1,cmp);
    int L=1,R=0;
    for(int i=1;i<=k;i++)
    {
        while(R<Q[i].r)
        {
            R++;
            add(R);
        }
        while(L>Q[i].l)
        {
            L--;
            add(L);
        }
        while(L<Q[i].l)
        {
            del(L);
            L++;
        }
        while(R>Q[i].r)
        {
            del(R);
            R--;
        }
        ans[Q[i].id]=Ans;
    }
    for(int i=1;i<=k;i++)
        printf("%d\n",ans[i]);
    return 0;
}

2.set强行存

(如果知道这也可以卡过去那这题应该过了的2333333)

对于每个点开一个set,自下而上合并set就好了。

代码:(加了个快读,不然被卡了)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
#include <set>
using namespace std;

const int maxn=1e5+10;

struct ios {
    inline char read(){
        static const int IN_LEN=1<<18|1;
        static char buf[IN_LEN],*s,*t;
        return (s==t)&&(t=(s=buf)+fread(buf,1,IN_LEN,stdin)),s==t?-1:*s++;
    }

    template <typename _Tp> inline ios & operator >> (_Tp&x){
        static char c11,boo;
        for(c11=read(),boo=0;!isdigit(c11);c11=read()){
            if(c11==-1)return *this;
            boo|=c11=='-';
        }
        for(x=0;isdigit(c11);c11=read())x=x*10+(c11^'0');
        boo&&(x=-x);
        return *this;
    }
} io;

int color[maxn];
vector<int>G[maxn];
int vis[maxn];
set<int>s[maxn];
int n,k;
int ans[maxn];
void init(){
    for(int i=0;i<maxn;i++){
        s[i].clear();
    }
}
void Union(set<int> &a,set<int> &b){
    set<int>::iterator it=b.begin();
    for(;it!=b.end();it++){
        a.insert(*it);
    }
}
void dfs(int u){
    vis[u]=1;
    s[u].insert(color[u]);
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(!vis[v]){
            dfs(v);
            Union(s[u],s[v]);
        }
    }
}
int main(){
    io>>n>>k;
    for(int i=1;i<=n;i++){
        io>>color[i];
    }
    for(int i=1;i<n;i++){
        int u,v;
        io>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    memset(vis,0,sizeof(vis));
    dfs(1);
    for(int i=1;i<=k;i++){
        int u;
        io>>u;
        printf("%d\n",s[u].size());
    }
    return 0;
}

3.树上启发式合并。

这种题的变种有很多都是用树上启发式合并完成的,学习一下很有必要,不过需要掌握数链剖分的技巧,等学完树链剖分再来吧。

大神博客传送门:https://blog.csdn.net/qq_41357771/article/details/80751150

                             https://www.cnblogs.com/zzqsblog/p/6146916.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值