雨天的尾巴
时间限制:10s 空间限制:128MB
题目描述
N个点,形成一个树状结构。有M次发放,每次选择两个点x,y
对于x到y的路径上(含x,y)每个点发一袋Z类型的物品。完成
所有发放后,每个点存放最多的是哪种物品。
输入格式
第一行数字N,M
接下来N-1行,每行两个数字a,b,表示a与b间有一条边
再接下来M行,每行三个数字x,y,z.如题
输出格式
输出有N行
每i行的数字表示第i个点存放最多的物品是哪一种,如果有
多种物品的数量一样,输出编号最小的。如果某个点没有物品
则输出0
样例输入
20 50
8 6
10 6
18 6
20 10
7 20
2 18
19 8
1 6
14 20
16 10
13 19
3 14
17 18
11 19
4 11
15 14
5 18
9 10
12 15
11 14 87
12 1 87
14 3 84
17 2 36
6 5 93
17 6 87
10 14 93
5 16 78
6 15 93
15 5 16
11 8 50
17 19 50
5 4 87
15 20 78
1 17 50
20 13 87
7 15 22
16 11 94
19 8 87
18 3 93
13 13 87
2 1 87
2 6 22
5 20 84
10 12 93
18 12 87
16 10 93
8 17 93
14 7 36
7 4 22
5 9 87
13 10 16
20 11 50
9 16 84
10 17 16
19 6 87
12 2 36
20 9 94
9 2 84
14 1 94
5 5 94
8 17 16
12 8 36
20 17 78
12 18 50
16 8 94
2 19 36
10 18 36
14 19 50
4 12 50
样例输出
87
36
84
22
87
87
22
50
84
87
50
36
87
93
36
94
16
87
50
50
1<=N,M<=100000
1<=a,b,x,y<=N
1<=z<=10^9
【分析】
不难想到树上差分
树上每个点的信息:最多的种类号和对应的数量
直接暴力统计的话需要每个点开桶,显然不行
注意到这个性质符合区间加,所以可以用线段树维护每个点的信息(但还是觉得很奇怪,因为这题其实只有修改,查询可以认为没有,那么用线段树总觉得有点浪费,所以其实有一种更优秀的解法就是利用树链剖分将树上问题转化为序列进行差分)
线段树虽然能维护每个点的信息,但是最后查询的时候需要的话求树上的前缀和,也就是子树和,所以考虑线段树合并。
确定基本做法后有几个问题:
①空间
显然不能每个点都开满线段树,那样就直接n2的空间复杂度了,需要利用动态开店优化空间,由于每次修改就会更新一条链,树高是logn所以是nlogn的空间复杂度,当然别忘了乘4
②合并
有专门的写法,这里考虑将儿子合并到父亲,可以简化程序
③离散化
显然z太大了需要离散化,不多说
【代码】
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<functional>
#define maxn 100039
using namespace std;
void read(int &x){
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
int n, m;
int head[maxn], idg, dep[maxn], h[maxn], h2[maxn];
int f[maxn][39];
struct FLY{
int to, nex;
}edges[maxn<<1];
void add(int from, int to){
edges[++idg].to = to;
edges[idg].nex = head[from];
head[from] = idg;
}
//LCA
int log2(int x){return int(log(x)/(log(2)));}
void dfs(int u){
for(int i = head[u]; i; i = edges[i].nex){
int v = edges[i].to;
if(dep[v])continue;
f[v][0] = u;
for(int i = 1; i < log2(n)+1; i++)
f[v][i] = f[f[v][i-1]][i-1];
dep[v] = dep[u]+1;
dfs(v);
}
}
int LCA(int x, int y){
if(dep[x]<dep[y]){int tmp = x; x = y; y = tmp;}
for(int i = log2(n); i > -1; i--)
if(dep[f[x][i]]>=dep[y])x = f[x][i];
if(x==y)return x;
for(int i = log2(n); i > -1; i--)
if(f[x][i]!=f[y][i])x = f[x][i], y = f[y][i];
return f[x][0];
}
const int MAXN = 100039*70;
int lson[MAXN<<2], rson[MAXN<<2], idt, sum[MAXN<<2], kind[MAXN<<2], root[maxn], ans[maxn];
void pushup(int x){ //上推函数,注意要老老实实考虑左右儿子的情况,不要偷懒想着能自动化处理
//因为即使sum为负数,还是有可能有最大的种类值出现的
if(lson[x]==0){
sum[x]=sum[rson[x]];
kind[x]=kind[rson[x]];
return;
}
if(rson[x]==0){
sum[x]=sum[lson[x]];
kind[x]=kind[lson[x]];
return;
}
if(sum[lson[x]]>=sum[rson[x]]){
sum[x]=sum[lson[x]];
kind[x]=kind[lson[x]];
}else{
sum[x]=sum[rson[x]];
kind[x]=kind[rson[x]];
}
return;
}
//插入
void insert(int &u, int L, int R, int pos, int val){
if(!u)u = ++idt; //动态开点
if(L>=R){ //叶子的情况,直接赋值
sum[u] += val;
kind[u] = pos;
return;
}
int m = (L+R)>>1;
if(pos<=m)insert(lson[u], L, m, pos, val);
else insert(rson[u], m+1, R, pos, val);
pushup(u);
return;
}
//合并,将b合并到a,注意此处传地址
void merge(int &a, int &b, int L, int R){
if(!b)return; //b是空的话啥都不用做
if(!a){ //a是空的话b变成a
a = b;
return;
}
if(L>=R){ //都有的话合并到a,别问我为什么没有考虑kind的编号,其实应该写的,不然可能会被HACK
//毕竟题目要求相同的话编号要尽可能小,但是没处理发现也过了就没去管,,,
if(kind[a]==kind[b])sum[a]+=sum[b]; //同种就合并
else if(sum[a]<sum[b]){ //不同的话看哪个大
sum[a]=sum[b];
kind[a] = kind[b];
}
return;
}
int m = (L+R)>>1;
merge(lson[a], lson[b], L, m);
merge(rson[a], rson[b], m+1, R);
pushup(a); //这里也要上推,因为左右儿子都合并好了,所以线段树合并本质是叶子节点的合并+上推
return;
}
void cal(int u, int pre){
for(int i = head[u]; i; i = edges[i].nex){
int v = edges[i].to;
if(v==pre)continue;
cal(v, u);
merge(root[u], root[v], 1, 100000); //一边回溯一边合并儿子
}
ans[u] = kind[root[u]]; //根节点的信息就是答案
return;
}
int x[maxn], y[maxn], z[maxn], zz[maxn], id[maxn];
int main(){
//freopen("1.in", "r", stdin);
scanf("%d%d", &n, &m);
int from, to, w;
for(int i = 1; i < n; i++){
read(from); read(to);
add(from, to);
add(to, from);
}
dep[1] = 1;
dfs(1);
for(int i = 1; i < m+1; i++){
read(x[i]); read(y[i]); read(z[i]);
zz[i] = z[i];
}
//离散化,复制数组后直接lower_bound
sort(zz+1, zz+1+m);
for(int i = 1; i < m+1; i++)h[i] = lower_bound(zz+1, zz+1+m, z[i])-zz, h2[h[i]] = z[i];
for(int i = 1; i < m+1; i++){
int lca = LCA(x[i], y[i]);
//4个差分,考虑通过LCA形成两条路径即可
insert(root[x[i]], 1, 100000, h[i], 1);
insert(root[y[i]], 1, 100000, h[i], 1);
insert(root[lca], 1, 100000, h[i], -1);
insert(root[f[lca][0]], 1, 100000, h[i], -1);
}
cal(1, -1);
//h2记录离散化前的值
for(int i = 1; i < n+1; i++){
printf("%d\n", h2[ans[i]]);
}
return 0;
}