• Blood Cousins Return


    题意:

    给定一棵树,给一些询问询问一个节点 (x) 子树里,与 (x) 距离为 (k)(k) 不同)节点的值有多少种。
    (n) 个点,(m) 次查询。
    数据范围:(1 ≤ n ≤ 10^5,1 ≤ m ≤ 10^5)

    传送门

    分析:

    树上启发式合并。
    一个离线算法,总复杂度一般是 (O(nlogn)) 的,要么就乘上一个统计的复杂度。
    此题中用一个 (multiset) 维护每个深度的所有节点值,并用数组维护每个深度不同的值的个数。
    一开始总是超时,后来发现,因为题目中是以 (0) 为根,而实际上不用考虑这个点,而我在启发式合并的时候去算了这个点,导致超时。
    复杂度:(O(nlog^2n))

    代码:

    #include <bits/stdc++.h>
    #define pb push_back
    using namespace std;
    typedef pair<int,int>P;
    const int N=1e5+10;
    int ans[N],son[N],sz[N],name[N],depth[N],num[N];
    int nt;
    multiset<int>st[N];
    vector<P>nd[N];
    map<string,int>mp;
    struct node
    {
        int to,nxt;
    }edge[N<<1];
    int head[N];
    int cnt;
    void init()
    {
        memset(head,-1,sizeof(head));
        cnt=1;
    }
    void addedge(int from,int to)
    {
        edge[cnt].to=to;
        edge[cnt].nxt=head[from];
        head[from]=cnt++;
    }
    void read(int &x)
    {
        x=0;
        int f=1;
        char ch=getchar();
        while(!isdigit(ch))
        {
            if(ch=='-')
                f=-1;
            ch=getchar();
        }
        while(isdigit(ch))
        {
            x=(x<<3)+(x<<1)+ch-'0';
            ch=getchar();
        }
        x*=f;
    }
    void dfs1(int v,int d)
    {
        sz[v]=1;
        son[v]=-1;
        depth[v]=d;
        for(int i=head[v];i!=-1;i=edge[i].nxt)
        {
            int u=edge[i].to;
            dfs1(u,d+1);
            sz[v]+=sz[u];
            if(son[v]==-1||sz[u]>sz[son[v]])
                son[v]=u;
        }
    }
    void add(int v)
    {
        if(st[depth[v]].count(name[v])==0)
            num[depth[v]]++;
        st[depth[v]].insert(name[v]);
        for(int i=head[v];i!=-1;i=edge[i].nxt)
        {
            int u=edge[i].to;
            add(u);
        }
    }
    void del(int v)
    {
        st[depth[v]].erase(st[depth[v]].find(name[v]));
        if(st[depth[v]].count(name[v])==0)
            num[depth[v]]--;
        for(int i=head[v];i!=-1;i=edge[i].nxt)
        {
            int u=edge[i].to;
            del(u);
        }
    }
    void solve(int v,bool f)
    {
        for(int i=head[v];i!=-1;i=edge[i].nxt)
        {
            int u=edge[i].to;
            if(u==son[v])
                continue;
            solve(u,false);
        }
        if(son[v]>-1)
            solve(son[v],true);
        if(v==0)
            return;
        for(int i=head[v];i!=-1;i=edge[i].nxt)
        {
            int u=edge[i].to;
            if(u==son[v])
                continue;
            add(u);
        }
        if(st[depth[v]].count(name[v])==0)
            num[depth[v]]++;
        st[depth[v]].insert(name[v]);
        for(int i=0;i<nd[v].size();i++)
        {
            P t=nd[v][i];
            if(t.first+depth[v]<N)
                ans[t.second]=num[t.first+depth[v]];
        }
        if(!f)
            del(v);
    }
    int main()
    {
        int n,m,r;
        scanf("%d",&n);
        init();
        for(int i=1;i<=n;i++)
        {
            char s[25]={};
            scanf("%s",s);
            read(r);
            if(mp.count(s)==0)
            {
                mp[s]=++nt;
                name[i]=nt;
            }
            else
                name[i]=mp[s];
            addedge(r,i);
        }
        int v,k;
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            read(v),read(k);
            nd[v].pb(make_pair(k,i));
        }
        dfs1(0,0);
        solve(0,false);
        for(int i=1;i<=m;i++)
            printf("%d
    ",ans[i]);
        return 0;
    }
    
    

    下面是网上看到的其它的做法:
    1.(dfs序+二分)
    利用 (dfs) 序的特点,把点按深度归类,同一棵子树中的点一定排列在一起。利用二分即可,注意序号。如果不存答案的话,会超时。

    #include <bits/stdc++.h>
    #define pb push_back
    using namespace std;
    typedef pair<int,int>P;
    const int N=1e5+5;
    vector<int>dn[N],pic[N];
    int sz[N],dfn[N],name[N],depth[N],rnk[N];
    map<P,int>ms;
    map<string,int>mp;
    void dfs(int v,int d,int &cnt)
    {
        sz[v]=1;
        rnk[cnt]=v;
        dfn[v]=cnt++;
        dn[d].pb(dfn[v]);
        depth[v]=d;
        for(int i=0;i<pic[v].size();i++)
        {
            int u=pic[v][i];
            dfs(u,d+1,cnt);
            sz[v]+=sz[u];
        }
    }
    int solve(int v,int k,int n)
    {
        if(ms[make_pair(v,k)]>0)
            return ms[make_pair(v,k)];
        if(depth[v]+k>n||dn[depth[v]+k].empty())
            return ms[make_pair(v,k)]=0;
        set<int>st;
        int a=dfn[v],b=dfn[v]+sz[v]-1;
        int l=lower_bound(dn[depth[v]+k].begin(),dn[depth[v]+k].end(),a)-dn[depth[v]+k].begin();
        int r=upper_bound(dn[depth[v]+k].begin(),dn[depth[v]+k].end(),b)-dn[depth[v]+k].begin()-1;
        for(int i=l;i<=r;i++)
            st.insert(name[rnk[dn[depth[v]+k][i]]]);
        return ms[make_pair(v,k)]=st.size();
    }
    int main()
    {
        int n,m,r,cot=0,cnt=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            char s[25]={};
            scanf("%s",s);
            scanf("%d",&r);
            if(mp.count(s)==0)
            {
                mp[s]=++cot;
                name[i]=mp[s];
            }
            else
                name[i]=mp[s];
            pic[r].pb(i);
        }
        dfs(0,0,cnt);
        int x,y;
        scanf("%d",&m);
        while(m--)
        {
            scanf("%d%d",&x,&y);
            printf("%d
    ",solve(x,y,n));
        }
        return 0;
    }
    
    

    2.主席树+二分+倍增
    传送门
    利用树的程序遍历,把相同深度的点放在一起,同时按深度把点分别存储,即转化为主席树求区间内不同数的个数问题,关键在于求区间左右端点。
    利用二分,对要求深度的点就行二分,判断条件为其父亲节点是否为 (v),用倍增查找。

  • 相关阅读:
    Matching「CEOI2011」
    中位数之中位数「NOIP多校联考 2019」
    积木「CSP-S全国排位赛第一场」
    序列「CSP-S全国排位赛第一场」
    GT考试「HNOI2008」
    动物园「NOI2014」
    Radio Transmission「BOI2009」
    小Z的袜子「国家集训队」
    BZOJ3624: [Apio2008]免费道路
    BZOJ1190: [HNOI2007]梦幻岛宝珠
  • 原文地址:https://www.cnblogs.com/1024-xzx/p/12773278.html
Copyright © 2020-2023  润新知