雨天的尾巴
题目背景
深绘里一直很讨厌雨天。
灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。
虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。
无奈的深绘里和村民们只好等待救济粮来维生。
不过救济粮的发放方式很特别。
题目描述
首先村落里的一共有 n n n 座房屋,并形成一个树状结构。然后救济粮分 m m m 次发放,每次选择两个房屋 ( x , y ) (x,~y) (x, y),然后对于 x x x 到 y y y 的路径上(含 x x x 和 y y y)每座房子里发放一袋 z z z 类型的救济粮。
然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
输入格式
输入的第一行是两个用空格隔开的正整数,分别代表房屋的个数 n n n 和救济粮发放的次数 m m m。
第 2 2 2 到 第 n n n 行,每行有两个用空格隔开的整数 a , b a,~b a, b,代表存在一条连接房屋 a a a 和 b b b 的边。
第 ( n + 1 ) (n + 1) (n+1) 到第 ( n + m ) (n + m) (n+m) 行,每行有三个用空格隔开的整数 x , y , z x,~y,~z x, y, z,代表一次救济粮的发放是从 x x x 到 y y y 路径上的每栋房子发放了一袋 z z z 类型的救济粮。
输出格式
输出 n n n 行,每行一个整数,第 i i i 行的整数代表 i i i 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。
如果某座房屋没有救济粮,则输出 0 0 0。
样例 #1
样例输入 #1
5 3
1 2
3 1
3 4
5 3
2 3 3
1 5 2
3 3 3
样例输出 #1
2
3
3
0
2
提示
- 对于 20 % 20\% 20% 的数据,保证 n , m ≤ 100 n, m \leq 100 n,m≤100。
- 对于 50 % 50\% 50% 的数据,保证 n , m ≤ 2 × 1 0 3 n, m \leq 2 \times 10^3 n,m≤2×103。
- 对于 100 % 100\% 100% 测试数据,保证 1 ≤ n , m ≤ 1 0 5 1 \leq n, m \leq 10^5 1≤n,m≤105, 1 ≤ a , b , x , y ≤ n 1 \leq a,b,x,y \leq n 1≤a,b,x,y≤n, 1 ≤ z ≤ 1 0 5 1 \leq z \leq 10^5 1≤z≤105。
题解
对于一次操作a,b,z。我们求出a,b的lca,一个很暴力的想法是,我们开一个二维数组g[i][j],利用树上差分,我们使g[a][z]+1,g[b][z]+1,g[lca(a,b)]-1,g[fa[lca(a,b)]]-1。最后dfs自底向上求和,此时的g[i][j]就是点i有多少个j。题目最后要我们求每个点最多的物品是什么,就是求一个序列的最大值。如果一点优化没有的话,时空复杂度是n2,时间复杂度是n2。
接下来考虑如何优化。
1.如何加速求一个序列的最大值:可以使用线段树进行维护,复杂度是nlogn。
2.如何减小空间复杂度。我们如果为1e5个点都开一个区间长度为1e5的线段树,空间肯定会爆炸,但是我们发现我们并不需要每个点都开一个完整的线段树,我们只开辟用到的节点即可,所以我们可以用动态开点的方式构建线段树,现在我们分析一下空间复杂度,我们一共有1e5*4次操作,每次操作最多开辟logn个节点≈17。故我们的节点数最多为17*4e5,大约90M左右。
3.如果使用动态开点的线段树,如何计算最后的答案:我们还是遵循最初的分析,使用dfs自底向上求和,对应到线段树就是自底向上进行合并。进行合并时,线段树两两合并最后变为一颗线段树,合并次数不会超过所有线段树的节点个数+1。故时间复杂度是nlogn。
附上代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10,M = N<<1;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int n,m;
int depth[N],f[N][17],q[N];
int fa[N],ans[N];
void bfs(){
memset(depth,0x3f,sizeof depth);
depth[0]=0,depth[1]=1;
int hh=0,tt=0;
q[tt++]=1;
while(hh!=tt){
int t=q[hh++];
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(depth[j]>depth[t]+1){
fa[j]=t;
depth[j]=depth[t]+1;
q[tt++]=j;
f[j][0]=t;
for(int k=1;k<17;k++)f[j][k]=f[f[j][k-1]][k-1];
}
}
}
}
int lca(int a,int b){
if(depth[a]<depth[b])swap(a,b);
for(int i=16;i>=0;i--)
if(depth[f[a][i]]>=depth[b])a=f[a][i];
if(a==b)return a;
for(int i=16;i>=0;i--){
if(f[a][i]!=f[b][i]){
a=f[a][i],b=f[b][i];
}
}
return f[a][0];
}
int tot,root[N];
struct Node{
int lc,rc;
int mx,pos;
}tr[N*70];
int build(){
tot++;
tr[tot].lc=tr[tot].rc=tr[tot].mx=0;
return tot;
}
void pushup(int p){
if(tr[tr[p].lc].mx>=tr[tr[p].rc].mx){
tr[p].mx=tr[tr[p].lc].mx;
tr[p].pos=tr[tr[p].lc].pos;
}
else{
tr[p].mx=tr[tr[p].rc].mx;
tr[p].pos=tr[tr[p].rc].pos;
}
}
void insert(int p,int l,int r,int k,int v,int rt){
//if(rt==3)cout<<l<<" "<<r<<" "<<k<<endl;
if(l==r){
tr[p].mx+=v;
tr[p].pos=l;
//if(rt==3)cout<<tr[p].mx<<"@@@"<<tr[p].pos<<endl;
return;
}
int mid=l+r>>1;
if(k<=mid){
if(!tr[p].lc)tr[p].lc=build();
insert(tr[p].lc,l,mid,k,v,rt);
}
else{
if(!tr[p].rc)tr[p].rc=build();
insert(tr[p].rc,mid+1,r,k,v,rt);
}
pushup(p);
//if(rt==3)cout<<tr[p].pos<<endl;
}
int merge(int p,int q,int l,int r){
if(!p)return q;
if(!q)return p;
if(l==r){
tr[p].pos=l;
tr[p].mx+=tr[q].mx;
return p;
}
int mid=l+r>>1;
tr[p].lc=merge(tr[p].lc,tr[q].lc,l,mid);
tr[p].rc=merge(tr[p].rc,tr[q].rc,mid+1,r);
pushup(p);
return p;
}
void dfs(int u){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa[u])continue;
dfs(j);
root[u]=merge(root[u],root[j],1,100000);
}
if(tr[root[u]].mx)ans[u]=tr[root[u]].pos;
else ans[u]=0;
}
int main(){
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
bfs();
for(int i=0;i<=n;i++){
root[i]=build();
}
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
int lc=lca(a,b);
insert(root[a],1,100000,c,1,root[a]);
insert(root[b],1,100000,c,1,root[b]);
insert(root[lc],1,100000,c,-1,root[lc]);
insert(root[fa[lc]],1,100000,c,-1,root[fa[lc]]);
}
dfs(1);
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
return 0;
}