莫队 + dfs序 + 离散化处理 HDU 4358

 

Boring counting

 

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 98304/98304 K (Java/Others)
Total Submission(s): 2902    Accepted Submission(s): 866

 

 

Problem Description

In this problem we consider a rooted tree with N vertices. The vertices are numbered from 1 to N, and vertex 1 represents the root. There are integer weights on each vectice. Your task is to answer a list of queries, for each query, please tell us among all the vertices in the subtree rooted at vertice u, how many different kinds of weights appear exactly K times?

 

 

Input

The first line of the input contains an integer T( T<= 5 ), indicating the number of test cases.
For each test case, the first line contains two integers N and K, as described above. ( 1<= N <= 105, 1 <= K <= N )
Then come N integers in the second line, they are the weights of vertice 1 to N. ( 0 <= weight <= 109 )
For next N-1 lines, each line contains two vertices u and v, which is connected in the tree.
Next line is a integer Q, representing the number of queries. (1 <= Q <= 105)
For next Q lines, each with an integer u, as the root of the subtree described above.

 

 

Output

For each test case, output "Case #X:" first, X is the test number. Then output Q lines, each with a number -- the answer to each query.

Seperate each test case with an empty line.

 

 

Sample Input


 

1 3 1 1 2 2 1 2 1 3 3 2 1 3

 

 

Sample Output


 

Case #1: 1 1 1

 

 

 

 

题意:有一棵树,每个点有一个权值,m次询问,问这个点的子树里权值出现 k 次的权值个数

思路:

一棵树,每次对询问的节点去遍历它的儿子并计数的话 10^5次询问,每次询问根节点,那时间百分之百的不够了

对于子树问题的,我们都可以打出一个dfs序把它转化为区间问题,这题也不例外,打了dfs序之后就成了区间问题了

然后使用莫队就妥妥地搞定,但是注意题目给的权值是到了 10^9,而并没有那么多节点,而且你开不了10^9数组去记录该值出现的次数。所以要离散化处理。离散化处理的方法有两种,一种是用map,但是这种会耗时间一点,另一种就是去打个离散化,重新给它赋值,因为节点数不超过10^5,所以打个10^5数组存就可以了。

注意:我们在把树进行dfs序操作之后,对于每个点它的左标签不一定是等于他原本的下标的,所以如果还按照原本的权值去操作的话就搓了。

比如 这组数据 :

5 2

1 1 2 10 10

1 3

1 2

2 4

2 5

5

1 2 3 4 5

这个树 打了dfs序之后 4 这个点的 左标签 右标签分别是 3,所以这时候就不能直接传 val [ x ] 了,要做一个操作

在打 dfs序的过程中 拿一个数组 val2[] 去存新的权值 val2[tm] = val1[now]

 

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
using namespace std;
#define ll long long
#define mem(a,x) memset(a,x,sizeof(a))
#define maxn 100005

int n,m,k,tm,Ans;
struct trie{
	int l,r;
}tree[maxn];
map<int,int>mp;
int val1[maxn],val2[maxn];
vector<int>G[maxn];
struct question{
	int l,r,id,pos;
}q[maxn]; 
bool cmp(question a,question b){
	if(a.pos != b.pos){
		return a.pos < b.pos;
	}
	return a.r < b.r;
}
void dfs(int now,int pre){
	tree[now].l = ++tm;
	val2[tm] = val1[now];  // 做了dfs序之后,对应下标的值要重新赋 
	int len = G[now].size();
	for(int i = 0;i < len;i++){
		if(G[now][i] != pre)
			dfs(G[now][i],now);
	}
	tree[now].r = tm;
	return ;
}
int ans[maxn],flag[maxn];
void add(int x){
	if(flag[x] == k)
		Ans--;
	flag[x]++;
	if(flag[x] == k)
		Ans++;
}
void dele(int x){
	if(flag[x] == k) 
		Ans--;
	flag[x]--;
	if(flag[x] == k) 
		Ans++;
}
int main(){
	int T,Case = 1,u,v,cnt,x;
	scanf("%d",&T);
	while(T--){
		scanf("%d %d",&n,&k);
		cnt = 0;
		mp.clear();
		for(int i = 1;i <= n;i++){
			scanf("%d",&x); 
			if(!mp.count(x))	// 离散化赋值 
				mp[x] = ++cnt;
			val1[i] = mp[x];
		}
		for(int i = 1;i <= n;i++){
			G[i].clear();
		}
		for(int i = 1;i < n;i++){
			scanf("%d %d",&u,&v);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		tm = 0;
		dfs(1,-1);	// 打dfs序 
		scanf("%d",&m);
		int block = sqrt(n);
		for(int i = 1;i <= m;i++){
			scanf("%d",&u);
			q[i].l = tree[u].l;	//把询问做 dfs序赋值 
			q[i].r = tree[u].r;
			q[i].pos = tree[u].l / block; //分块 
			q[i].id = i;
		}
		sort(q + 1,q + 1 + m,cmp);	//分块排序 
		mem(flag,0);
		int l = 1,r = 0;
		Ans = 0;
		for(int i = 1;i <= m;i++){
			while(r < q[i].r)
				add(val2[++r]);
			while(r > q[i].r)
				dele(val2[r--]);
			while(l < q[i].l)
				dele(val2[l++]);
			while(l > q[i].l)
				add(val2[--l]);
			ans[q[i].id] = Ans;
		}
		printf("Case #%d:\n",Case++);
		for(int i = 1;i <= m;i++)
			printf("%d\n",ans[i]);
		if(T != 0)
			puts("");
	}
	return 0;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值