题目描述(简约版)
N个点,形成一个树状结构。有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品。完成所有发放后,每个点存放最多的是哪种物品。
注释很全,请耐心看一看,写了好久哒
#include<bits/stdc++.h>
using namespace std;
#define lson t[rt].ls
#define rson t[rt].rs
const int maxn=1e5+10;
struct stu
{
int ls,rs;//左右子树
int num;//物品个数的众数
int w;//个数最多的物品是哪种
}t[maxn*80];
int n,m,f[maxn][30],dep[maxn];
int h[maxn],to[maxn*2],nxt[maxn*2],tot;
int root[maxn],segtot,ans[maxn];
bool vis[maxn];
void add(int x,int y)//加边
{
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
}
void dfs1(int x,int fa)
{
vis[x]=true;//遍历过哩记录一下
dep[x]=dep[fa]+1;//比父节点深度+1
for(int j=1;(1<<j)<=n;j++)//倍增lca
{
f[x][j]=f[f[x][j-1]][j-1];
}
for(int i=h[x];i;i=nxt[i])
{
if(!vis[to[i]])//根据刚刚的记录,如果遍历过了就跳过
//因为是双向边,所以需要判一下
{
f[to[i]][0]=x;//子节点to[i]向上跳一个深度就是父节点
//0是2的0次方也就是1
dfs1(to[i],x);//继续遍历下一个
}
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);//反正只是求最近公共祖先,所以换一下也没关系
//这样可以直接认为x深度就是比y小的,不用分两种情况了
int d=dep[x]-dep[y];//记录两个节点深度的差值,先跳到同一深度,再一起向上跳
for(int i=0;i<30;i++)
{
if(d&(1<<i)) x=f[x][i];//如果跳i深度还是比d小那就能跳,否则就不能跳了
//否则就跳过了
}
if(x==y) return x;//这里判的是如果正好一个节点是另一个的祖先
for(int i=29;i>=0;i--)
{
if(f[x][i]!=f[y][i])//这里只能判!=,要是==的话,可能不是最近公共祖先
//可能会跳过因为是从最大开始的
{
x=f[x][i];//都同时向上跳一样的深度
y=f[y][i];
}
if(f[x][0]==f[y][0]) return f[x][0];//如果两个不同子节点的父节点相同
//说明已经到了要求的父节点了直接就能结束循环了
}
}
void pushup(int rt)//向上更新
{
if(t[lson].num>=t[rson].num)//这就很显而易见了吧,哪个大就取哪个子树的信息喽
{
t[rt].num=t[lson].num;//把num和w全都附上最大值
t[rt].w=t[lson].w;
}
else
{
t[rt].num=t[rson].num;
t[rt].w=t[rson].w;
}
}
void update(int &rt,int l,int r,int pos,int val)//这里解释一下l和r虽然代指区间
//但是实际就是物品种类,后面会具体体现,pos代表物品种类,val直接就是1
//相当于这种物品在这个权值线段树中加1
{
if(!rt) rt=++segtot;//如果还没有以rt为根的子树,就新建一个叭
//这体现了动态开点的特点
if(l==r)//区间长度为1,表明已经递归到最底层可以开始赋值啦
{
t[rt].w=l;//这里用l,r,pos都是一样的
t[rt].num+=val;//这种物品个数加1
return;//别忘了返回
}
int mid=(l+r)>>1;//二分向下找pos
if(pos<=mid) update(lson,l,mid,pos,val);//这个就是二分查找了吧...?
else update(rson,mid+1,r,pos,val);
pushup(rt);//向上更新众数和物品种类
}
int merge(int ra,int rb,int l,int r)//合并
{
if(!ra||!rb) return ra|rb;//如果一个子树w物品为空就返回有值的一个
if(l==r)//如果找到w物品且都不为空
{
t[ra].num+=t[rb].num;//将两个物品个数直接相加最后一定>=0
return ra;//返回
}
int mid=(l+r)>>1;
t[ra].ls=merge(t[ra].ls,t[rb].ls,l,mid);//二分向下更新子节点
t[ra].rs=merge(t[ra].rs,t[rb].rs,mid+1,r);
pushup(ra);//向上更新父节点
return ra;
}
void dfs2(int x)//遍历求答案
{
vis[x]=true;
for(int i=h[x];i;i=nxt[i])
{
if(!vis[to[i]])//又用到哩
{
dfs2(to[i]);//不断往下遍历子节点
root[x]=merge(root[x],root[to[i]],1,maxn);//将子树的信息直接合并
//到父节点上,不用考虑别的之前都处理好了
}
}
if(t[root[x]].num) ans[x]=t[root[x]].w;//如果num不为零就赋给ans啦
}
int main()
{
int x,y,z;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);//加双向边就不用担心有遍历不到的了,就可以随便取根啦
add(y,x);
}
dfs1(1,0);//因为是无向图,就随便取一个点当根就好啦,且认为他的父节点为0
memset(vis,false,sizeof(vis));//dfs2还要用先重置一下
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
int t=lca(x,y);//求最近公共祖先
update(root[x],1,maxn,z,1);//记录的时候只将x和y将w物品个数+1
update(root[y],1,maxn,z,1);//其他先不管,以后再向上合并
update(root[t],1,maxn,z,-1);//记录最近公共祖先w物品-1
//因为合并的时候x和yw都有1个1
update(root[f[t][0]],1,maxn,z,-1);//最近公共祖先的父节点再-1
//合并的时候w本来没发在这个父节点,但合并的时候会合并上两个w
//最近公共祖先减了一次,父节点本来第i次没发放w,所以要减两次
//应该能理解吧......语文不好见谅吧QAQ
}
dfs2(1);//遍历一次求出每个点的众数
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);//依次输出
return 0;
}
本代码借鉴于https://www.cnblogs.com/zhagnxinyue/p/18184950
注释全由本蒟蒻手打