链接: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