树上数数 题解

一、题目:

题目链接

个人私题,请注意版权保护。

二、思路:

树上莫队经典例题。其莫队的思想也很经典。

首先考虑在树上怎样莫队。我们很熟悉在序列上进行莫队,那么我们考虑将树转化成序列。那么什么序列能将子树转化为一个区间呢?当然是dfs序了!

然后回忆HH的项链那道经典题。它问的是“一段区间所包含的不同颜色的数量”,我们开桶cnt[i]保存i颜色出现的数量。而这道题我们问的是“出现次数大于等于K的颜色数量”,cnt数组还是要有的,还需要什么?我们很自然地想到用某个数据结构sum高效计算出现次数大于等于i的颜色数量。

那么怎么高效维护这个数据结构呢?首先很容易想到的是log级数据结构,比如权值线段树或权值树状数组。sum[i]表示出现次数为i的颜色数量,比如对于当前扫到的颜色col,每次在更新cnt[col]的时候记原先的cnt[col]为former,更新后的cnt[col]为now,将sum[former]减一,将sum[now]加一。查询时只需查询[k,n]的区间和即可。这是可以通过本题的。时间复杂度\(O(n\sqrt n logn\)).

但是上述做法无论编程复杂度和时间复杂度都较高。我们考虑优化。更改sum的意义。sum[i]表示出现次数大于等于i的颜色数量。冷静分析。当我们准备将cnt[col]加一时,只有sum[cnt[col]+1]发生了变化,因为大于等于cnt[col]的颜色数量没有变,而大于等于cnt[col]+1的颜色数量却增加了一个。所以我们只需将sum[cnt[col]+1]++,然后将cnt[col]本身++即可。类似地,当我们准备将cnt[col]减一时,只有sum[cnt[col]]发生了变化,sum[cnt[col]-1]及其他的sum值均没有改变。时间复杂度\(O(n\sqrt n)\).

三、代码:

60分做法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>

#define LL long long
#define FILEIN(s) freopen(s".in","r",stdin)
#define FILEOUT(s) freopen(s".out","w",stdout)
#define mem(s,v) memset(s,v,sizeof(s))

using namespace std;
inline LL read(void){
    LL x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return f*x;
}

const int maxn=5005;

int n,m;

int col[maxn],b[maxn],TOT;

int head[maxn],tot;

int cnt[maxn][maxn];

struct Edge{
    int y,nxt;
    Edge(){}
    Edge(int _y,int _nxt):y(_y),nxt(_nxt){}
}e[maxn<<1];

struct Query{
    int v,k,ans;
}q[100005];

vector<int>query[maxn];

inline void connect(int x,int y){
    e[++tot]=Edge(y,head[x]);
    head[x]=tot;
}

void dfs(int x,int fa){
    for(register int i=head[x];i;i=e[i].nxt){
        int y=e[i].y;
        if(y==fa)continue;
        dfs(y,x);
        for(register int j=1;j<=TOT;++j){
            cnt[x][j]+=cnt[y][j];
        }
    }
    cnt[x][col[x]]++;
    for(register int i=0;i<(int)query[x].size();++i){
        int cur=query[x][i];
        for(register int col=1;col<=TOT;++col){
            if(cnt[x][col]>=q[cur].k)q[cur].ans++;
        }
    }
}

int main(){
//  FILEIN("count");FILEOUT("count");
    n=read();m=read();
    for(register int i=1;i<=n;++i){
        col[i]=read();
        b[++TOT]=col[i];
    }
    sort(b+1,b+TOT+1);
    TOT=unique(b+1,b+TOT+1)-b-1;
    for(register int i=1;i<=n;++i){
        col[i]=lower_bound(b+1,b+TOT+1,col[i])-b;
    }
    for(register int i=1;i<n;++i){
        int x=read(),y=read();
        connect(x,y);connect(y,x);
    }
    for(register int i=1;i<=m;++i){
        q[i].v=read();q[i].k=read();q[i].ans=0;
        query[q[i].v].push_back(i);
    }
    dfs(1,0);
    for(register int i=1;i<=m;++i){
        printf("%d\n",q[i].ans);
    }
    return 0;
}

满分做法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>

#define LL long long
#define FILEIN(s) freopen(s".in","r",stdin)
#define FILEOUT(s) freopen(s".out","w",stdout)
#define mem(s,v) memset(s,v,sizeof(s))

using namespace std;
inline LL read(void){
    LL x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return f*x;
}

const int maxn=100005;

int n,m;

int head[maxn],tot,block;
int col[maxn],siz[maxn],dfn[maxn],Cnt,pos[maxn];

int sum[maxn],cnt[maxn];

struct Edge{
    int y,nxt;
    Edge(){}
    Edge(int _y,int _nxt):y(_y),nxt(_nxt){}
}e[maxn<<1];

struct Query{
    int l,r,k,ans,id;
}q[maxn];

inline bool cmp1(Query a,Query b){
    if(a.l/block!=b.l/block)return a.l/block<b.l/block;
    return a.r<b.r;
}

inline bool cmp2(Query a,Query b){
    return a.id<b.id;
}

inline void connect(int x,int y){
    e[++tot]=Edge(y,head[x]);
    head[x]=tot;
}

void dfs(int x,int fa){
    dfn[++Cnt]=x;
    pos[x]=Cnt;
    siz[x]=1;
    for(register int i=head[x];i;i=e[i].nxt){
        int y=e[i].y;
        if(y==fa)continue;
        dfs(y,x);
        siz[x]+=siz[y];
    }
}

inline void del(int x){
    --sum[cnt[col[dfn[x]]]--];
}

inline void add(int x){
    ++sum[++cnt[col[dfn[x]]]];
}

int main(){
    n=read();m=read();
    block=sqrt(n);
    for(register int i=1;i<=n;++i){
        col[i]=read();
    }
    for(register int i=1;i<n;++i){
        int x=read(),y=read();
        connect(x,y);connect(y,x);
    }
    dfs(1,0);
    for(register int i=1;i<=m;++i){
        q[i].id=i;
        int v=read();
        q[i].k=read();
        q[i].l=pos[v];
        q[i].r=q[i].l+siz[v]-1;
        q[i].ans=0;
    }
    sort(q+1,q+m+1,cmp1);
    int l=1,r=0;
    for(register int i=1;i<=m;++i){
        if(r<q[i].r)while(r!=q[i].r)add(++r);
        if(r>q[i].r)while(r!=q[i].r)del(r--);
        if(l<q[i].l)while(l!=q[i].l)del(l++);
        if(l>q[i].l)while(l!=q[i].l)add(--l);
        q[i].ans=sum[q[i].k];
    }
    sort(q+1,q+m+1,cmp2);
    for(register int i=1;i<=m;++i){
        printf("%d\n",q[i].ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/little-aztl/p/11215476.html

时间: 2024-07-31 06:38:40

树上数数 题解的相关文章

COJ 0036 数数happy有多少个?

数数happy有多少个? 难度级别:B: 运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 图图是个爱动脑子.观察能力很强的好学生.近期他正学英语单词,练字时无意识地写了一串小写英文字母,他发现这串字母中包含了很多个happy,他决定计算一下到底有多少个happy.规则是这样的:在该字符串提取任意位置的字符组成新的单词串,不改变其在原字符串中的相对顺序,请你编写程序帮助图图统计出新的单词串种最多有多少个happy. 输入 输入只有一行,含有一

nyoj 数数

/*数数时间限制:3000 ms  |  内存限制:65535 KB 难度:2描述 我们平时数数都是喜欢从左向右数的,但是我们的小白同学最近听说德国人数数和我们有些不同,他们正好和我们相反,是从右向左数的.因此当他看到123时会说"321". 现在有一位德国来的教授在郑州大学进行关于ACM的讲座.现在他聘请你来担任他的助理,他给你一些资料让你找到这些资料在书中的页数.现在你已经找到了对应的页码,要用英文把页码告诉他. 为了简化我们的问题,你只需要返回单词的大写的首字母.(数字0读成字母

金色十月线上编程比赛第一题:小女孩数数

一个小女孩正在用左手手指数数,从1数到n.她从拇指算作1开始数起,然后,食指为2,中指为3,无名指为4,小指为5.接下来调转方向,无名指算作6,中指为7,食指为8,大拇指为9,如此反复.问最后会停在那个手指上?用编号1.2.3.4.5依次表示大拇指.食指.中指.无名指.小指. 输入格式: 输入多组数据.每组数据占一行,只包含一个整数n(1<=n<=1000000000). 输出格式: 每组数据占一行,只包含一个介于1和5之间的整数,表示最后停留的手指. 答题说明: 输入样例: 1 10 100

CSDN挑战编程——《金色十月线上编程比赛第一题:小女孩数数》

金色十月线上编程比赛第一题:小女孩数数 题目详情: [金色十月线上编程比赛规则] 一个小女孩正在用左手手指数数,从1数到n.她从拇指算作1开始数起,然后,食指为2,中指为3,无名指为4,小指为5.接下来调转方向,无名指算作6,中指为7,食指为8,大拇指为9,如此反复.问最后会停在那个手指上?用编号1.2.3.4.5依次表示大拇指.食指.中指.无名指.小指. 输入格式: 输入多组数据.每组数据占一行,只包含一个整数n(1<=n<=1000000000). 输出格式: 每组数据占一行,只包含一个介

【BZOJ】【3530】【SDOI2014】数数

AC自动机/数位DP orz zyf 好题啊= =同时加深了我对AC自动机(这个应该可以叫Trie图了吧……出边补全!)和数位DP的理解……不过不能自己写出来还真是弱…… 1 /************************************************************** 2 Problem: 3530 3 User: Tunix 4 Language: C++ 5 Result: Accepted 6 Time:1008 ms 7 Memory:33956 kb

【BZOJ 3530】 [Sdoi2014]数数

3530: [Sdoi2014]数数 Time Limit: 10 Sec Memory Limit: 512 MB Submit: 422 Solved: 250 [Submit][Status][Discuss] Description 我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串.例如当S=(22,333,0233)时,233是幸运数,2333.20233.3223不是幸运数. 给定N和S,计算不大于N的幸运数个数. Input 输入的第一

洛谷 P2646 数数zzy

P2646 数数zzy 题目描述 zzy自从数学考试连续跪掉之后,上数学课就从来不认真听了(事实上他以前也不认真听).于是他开始在草稿纸上写写画画,比如写一串奇怪的字符串.然后他决定理♂性♂愉♂悦♂一下:统计这串字符串当中共有多少个为“zzy”的子序列(注意是子序列而非子串).但是由于他写的字符串实在是太长啦,而且他是个超级大蒟蒻,根本就数不过来.所以他决定请求你这个超级大神犇的帮助.你可以帮帮他吗? 输入输出格式 输入格式: 一行仅含小写字母的字符串. 输出格式: 一行,一个非负整数,表示输入

Java练习 SDUT-3106_小鑫数数儿

小鑫数数儿 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 某天小鑫忽然得到了许多的数字,他很好学,老师给他布置了一个任务,求出这些数字中,小于他们平均数.等于他们平均数.大于他们平均数的数字的数量是多少.(对于出现的平均数,保证都是整数,不会出现浮点数) Input 多组输入. 对于每次的输入,第一行一个整数N(1 <= N <= 10),代表数字的个数. 接下来的一行,输入N个整数M(0 <= M <

关于“从零开始数数”

以前听一个老师说他听到的一个玩笑:有人说程序员都是从 0 开始数数的.当时只觉得这不过是一个不疼不痒的梗而已. 学了半个多月,我可远远称不上是"程序员",今天遇到件这样的事: 朋友:"哎,你快看那边!" 我:"哪儿?你说得具体一点." 朋友:"那边,第四排!" 我:"0,1,2,3,4--怎么了?" 朋友:"你怎么了?你原来的数学挺好的." 我:"--" 原文地址:h

LJJ爱数数

LJJ爱数数 求\(\sum_{i=1}^n\sum_{j=1}^n\sum_{k=1}^n\epsilon(gcd(i,j,k))(\frac{1}{i}+\frac{1}{j}==\frac{1}{k})\) 解 显然无法用Mobius反演,问题在于\(\frac{1}{i}+\frac{1}{j}==\frac{1}{k}\),要将其转换为gcd条件. 法一:先约数拆分,再证明对应相等 分数我们无法处理,所以有 \[(i+j)k=ij\] 设\(g=gcd(i,j),I=i/g,J=j/g