hdu-4358:Boring counting(优美算法之树上启发式合并)

通过这道题了解到了神奇的树上dsu算法,起因是最近想再学习一波线段树,然后就找了一套线段树总结的题目。在那个总结里面看到了这道题,感觉很有意思。然后晚上回了宿舍就和室友在讨论,然后室友告诉我树上启发式合并随便做???

当时一脸懵逼,树上启发式合并是什么鬼,其实个人对启发式合并还是有一点了解的。之前做了一道可持久化并查集,那里面并查集的合并没有用正常的路径压缩,是用判断两个并查集大小的方式进行合并,最后保证复杂度维持在nlogn。

但是树上启发式合并之前完全没有听说过。室友信誓旦旦的说这做法可以解决大部分的子树查询问题。表示怀疑,因为我最开始想到的解法其实是dfs序之后直接莫队。线段树我们都不懂怎么写= = 然后一起研究了一下线段树的写法,感觉确实太麻烦了,室友说他第二天要用dsu解决这道题。我第二天用了莫队A了,他用dsuA了。。。于是我就把他的资料拿过来研究了一下,发现树上dsu是真的神奇,搞懂以后就感觉是个纯暴力但是稍加修改后它的复杂度是nlogn!!!

但是这个算法的局限性也比较大,首先好像不能插入修改操作,其次的话只能应对子树的查询问题。而且怎么说呢,如果要查询的东西比较复杂的话感觉也比较难搞。

不过就算如此,也是一个非常优秀的算法了,在应对子树查询类问题的时候可以优先考虑一下。而且,技多不压身啊。。。

贴一下树上dsu的学习资料:

原文链接:CF原文

推荐博客:https://blog.csdn.net/QAQ__QAQ/article/details/53455462

推荐博客:https://www.cnblogs.com/zzqsblog/p/6146916.html

题目大意:

给你一颗树,每个结点有自己的权值,求权值恰好出现次数为k的个数。

解题思路:

dfs序后直接莫队或者线段树都可,这里用树上dsu来写 = = 

Ac代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int n,k,m,res,a[maxn],sum[maxn],sz[maxn],son[maxn],q[maxn],ans[maxn];
bool vis[maxn];
vector<int> v[maxn],rv;
int getid(int x) { return lower_bound(rv.begin(),rv.end(),x)-rv.begin()+1; }
void init()
{
    rv.clear();
    for(int i=0;i<=n;i++) v[i].clear();
    for(int i=0;i<=n;i++) sum[i]=son[i]=vis[i]=0;
}
void dfs1(int x,int fa) //求出每个结点的重儿子
{
    sz[x]=1;
    for(int i=0;i<v[x].size();i++)
    {
        int u=v[x][i];
        if(u==fa) continue;
        dfs1(u,x); sz[x]+=sz[u];
        if(sz[u]>sz[son[x]]) son[x]=u;
    }
}
void edit(int x,int fa,int sp)  //更新每个点的贡献
{
    if(sum[a[x]]==k) res--;
    sum[a[x]]+=sp;
    if(sum[a[x]]==k) res++;
    for(int i=0;i<v[x].size();i++)
    {
        int u=v[x][i];
        if(u==fa||vis[u]) continue;
        edit(u,x,sp);
    }
}
void dfs(int x,int fa,int sp)   //sp表示贡献是否被清空
{
    for(int i=0;i<v[x].size();i++)  //遍历所有非重儿子的结点
    {
        int u=v[x][i];
        if(u==fa||u==son[x]) continue;
        dfs(u,x,0); //sp传0
    }
    if(son[x]) dfs(son[x],x,1),vis[son[x]]=1;   //sp传1
    edit(x,fa,1);   //遍历x的所有结点 添加重儿子以外的贡献
    ans[x]=res; //记录答案
    if(son[x]) vis[son[x]]=0;   //重儿子的标记取消
    if(!sp) edit(x,fa,-1);  //贡献取消
}
int main()
{
    int QAQ,kase=0,flag=0;
    scanf("%d",&QAQ);
    while(QAQ--)
    {
        if(flag) printf("\n");
        scanf("%d%d",&n,&k);
        init();
        for(int i=1;i<=n;i++)   //这里把权值离散一下
            scanf("%d",&a[i]),rv.push_back(a[i]);
        sort(rv.begin(),rv.end()),rv.erase(unique(rv.begin(),rv.end()),rv.end());
        for(int i=1;i<=n;i++) a[i]=getid(a[i]);
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            v[x].push_back(y);
            v[y].push_back(x);
        }
        scanf("%d",&m);
        for(int i=1;i<=m;i++) scanf("%d",&q[i]);    //储存询问
        dfs1(1,0);dfs(1,0,0);   //遍历跑出结果
        printf("Case #%d:\n",++kase);
        for(int i=1;i<=m;i++)
            printf("%d\n",ans[q[i]]);
        flag=1;
    }
}

猜你喜欢

转载自blog.csdn.net/f2935552941/article/details/81319974