原题链接:
hdu
题意简述
给定一颗树,和一些操作。一次操作给定 u , v , c u,v,c u,v,c,表示从 u u u到 v v v的路径上的点都会新加一种颜色 c c c。最后询问每个点上那种颜色有的最多。
数据
输入
多组数据。对于每组数据:
n m
//点数,操作数(n,m<=10^5)
u v
u v
...
u v
//n-1行,表示u和v之间有边。1<=u,v<=n
u v c
u v c
...
u v c
//m行,表示操作.1<=u,v<=n,1<=c<=10^5
输入0 0停止
输出
多组数据。对于每组数据:
ans1
ans2
...
ansn
//输出每个点的答案
样例
输入
2 4
1 2
1 1 1
1 2 2
2 2 2
2 2 1
5 3
1 2
3 1
3 4
5 3
2 3 3
1 5 2
3 3 3
0 0
输出
1
2
2
3
3
0
2
思路
又是个树剖。树剖的题目,要先考虑区间怎么做,再考虑丢到树上。
part1. 区间
看到题目中说是到最后才统一询问一次,就考虑差分做了。
正常的差分:当我们对区间 [ l , r ] [l,r] [l,r]染颜色 c c c时,就在 l − 1 l-1 l−1上加上 1 1 1,在 r r r上加上 − 1 -1 −1。
然后我们就会发现,光 1 1 1和 − 1 -1 −1不行,我们还要区分开颜色。观察到颜色的范围是 100000 100000 100000。所以。。。
- 再加一维。
那显然是不行的。空间复杂度 O ( n 2 ) O(n^2) O(n2),开 1 0 10 10^{10} 1010的数组,显然爆了 -
同
一
个
差
分
数
组
维
护
不
同
的
颜
色
\color{#ff0000}{同一个差分数组维护不同的颜色}
同一个差分数组维护不同的颜色。我们暴力模拟一下上面的那个思路。设差分数组叫
r
e
c
rec
rec。那么对于一次染色操作,就是:
++rec[c][l-1],--rec[c][r]
。然后我们发现,除了必要的 l − 1 l-1 l−1和 r r r之外,剩下的如果打成数对,就是 ( c , 1 ) (c,1) (c,1)和 ( c , − 1 ) (c,-1) (c,−1)。那么,我们能否把这个东西放在一个差分数组上加呢?此时已经有点显然了。当然,如果你智商足够,不用我提示,你也很快就能想到一个方法:把它们变成 c c c和 − c -c −c。
!!!
是不是很神奇。。。但是这样就不能是加上去了,加上去的话我们只能得到一个和。但是我们要得到的是每个数有多少。那么我们就不能用一个简单的 i n t int int维护差分数组了,而是 v e c t o r < i n t > vector<int> vector<int>。当然原来的 + + , − − ++,-- ++,−−都要变成 p u s h _ b a c k push\_back push_back。
那么我们如何求答案捏。。。我们只要把原来的 c c c和 − c -c −c拆开,拆成原来的 ( c , 1 ) (c,1) (c,1)和 ( c , − 1 ) (c,-1) (c,−1)的形式。这个珂以用绝对值函数 a b s abs abs实现。
根据初中学的知识,一个数 x x x的绝对值就是数字部分,而 x ∣ x ∣ \frac{x}{|x|} ∣x∣x就是 x x x的符号部分。
然后我们就开一个数组
a
n
s
ans
ans,
a
n
s
[
c
]
ans[c]
ans[c]表示当前枚举到的前缀中,有多少种
c
c
c颜色。然后
i
i
i从
1
1
1到
n
n
n枚举。对于
r
e
c
[
i
]
rec[i]
rec[i]中的每一个数,我们把它拆开,然后ans[数字部分]+=符号部分
然后第
i
i
i个位置的答案呢。。。也就是
a
n
s
ans
ans中的最大值。
然后我们会发现这个求最大值还要
O
(
n
)
O(n)
O(n),不就
n
2
n^2
n2了么。。。不,别急啊,上线段树。考虑到标记的总数是
O
(
2
m
)
O(2m)
O(2m),所以
O
(
l
o
g
n
)
O(logn)
O(logn)加
2
m
2m
2m次,
O
(
l
o
g
n
)
O(logn)
O(logn)求
n
n
n次,就是
O
(
(
n
+
2
m
)
l
o
g
n
)
O((n+2m)logn)
O((n+2m)logn),是显然能过的。
然后最后不是说要求哪个颜色出现最多,而不是出现几次么。。。
我艹真tm毒瘤。但是这个对于线段树来说,就是个二分。。。(解释见代码)
part2. 放到树上
如何放到树上呢。。。
只要在树链剖分的时候,不断的加标记即珂。对于我们现在走到的
u
u
u,我们设最上面是
t
o
p
[
u
]
top[u]
top[u],那就是
r
e
c
[
D
F
S
i
d
[
t
o
p
[
u
]
]
]
.
p
u
s
h
_
b
a
c
k
(
c
)
rec[DFSid[top[u]]].push\_back(c)
rec[DFSid[top[u]]].push_back(c),
r
e
c
[
D
F
S
i
d
[
u
]
]
.
p
u
s
h
_
b
a
c
k
(
−
c
)
rec[DFSid[u]].push\_back(-c)
rec[DFSid[u]].push_back(−c)。其中
D
F
S
i
d
DFSid
DFSid是
D
F
S
DFS
DFS序。
然后最后记得,你枚举 i i i从 1 1 1到 n n n,计算标记前缀和的时候,你求的答案并不是点 i i i的答案,而是点 x x x的答案,其中 x x x的 D F S DFS DFS序是 i i i。那么我们就要处理一个 D F S i d DFSid DFSid数组的反函数(俗一点说,这个数组就是求那个数的 D F S DFS DFS序是它。在代码里面叫 O r i Ori Ori,即 o r i g i n origin origin的简写)。然后记录答案即珂。
代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define N 200100//别怪我开一倍,我只是RE多了,有心理阴影了
class Graph//图
{
public:
int head[N];
int EdgeCount;
struct Edge
{
int To,Label,Next;
}Ed[N<<1];
void clear()
{
memset(Ed,-1,sizeof(Ed));
memset(head,-1,sizeof(head));
EdgeCount=0;
}
void AddEdge(int u,int v,int w)
{
++EdgeCount;
Ed[EdgeCount]=(Edge){v,w,head[u]};
head[u]=EdgeCount;
}
int Start(int u)
{
return head[u];
}
int To(int u)
{
return Ed[u].To;
}
int Label(int u)
{
return Ed[u].Label;
}
int Next(int u)
{
return Ed[u].Next;
}
}G;void Add(int u,int v,int w){G.AddEdge(u,v,w);G.AddEdge(v,u,w);}
class SegmentTree//线段树
//单点修改,区间求最值
{
public:
struct node
{
int l,r;
int x;
}tree[N<<2];
#define ls index<<1
#define rs index<<1|1
#define L tree[index].l
#define R tree[index].r
#define X tree[index].x
#define lL tree[ls].l
#define lR tree[ls].r
#define lX tree[ls].x
#define rL tree[rs].l
#define rR tree[rs].r
#define rX tree[rs].x
void Update(int index)
{
X=max(lX,rX);
}
void BuildTree(int l,int r,int index)
{
L=l,R=r,X=0;
if (l==r) return;
int mid=(l+r)>>1;
BuildTree(l,mid,ls);
BuildTree(mid+1,r,rs);
Update(index);
}
void Add(int pos,int c,int index)
{
if (pos<L or R<pos) return;
if (L==R)
{
X+=c;
return;
}
Add(pos,c,ls);
Add(pos,c,rs);
Update(index);
}
int Query(int mx,int index)
//Query函数:求哪个位置是满足条件的最大值。在这个基础上还保证这个位置最靠前。
/*
说好要来解释呢QωQ。那好,我来解释了。
我们把区间一分为二(这个线段树已经帮我们做好了)
优先考虑左半边(因为要答案最靠前)。如果左半边的区间最大=mx,那么就说明左半边有相应的最大值。所以我们就去左半边找了。此时我们就不用考虑右半边了,因为肯定都没有左半边的答案靠前
如果左半边没有答案,即区间最值!=mx,那就只能去右半边找了。。。
这样就能找到了呢QωQ
*/
{
if (L==R) return L;
if (lX==mx) return Query(mx,ls);
else Query(mx,rs);
}
}T;
int deep[N],size[N],son[N],fa[N];
void DFS1(int u,int f)
{
fa[u]=f;
deep[u]=(f==-1)?1:deep[f]+1;
size[u]=1;
son[u]=-1;
int Max=-1;
for(int i=G.Start(u);~i;i=G.Next(i))
{
int v=G.To(i);
if (v!=f)
{
DFS1(v,u);
size[u]+=size[v];
if (size[v]>Max)
{
Max=size[v];
son[u]=v;
}
}
}
}
int DFSid[N];int cnt=0;
int Ori[N];
int top[N];
void DFS2(int u,int topu)
{
top[u]=topu;
DFSid[u]=++cnt;
Ori[cnt]=u;
if (son[u]==-1) return;
DFS2(son[u],topu);
for(int i=G.Start(u);~i;i=G.Next(i))
{
int v=G.To(i);
if (v!=son[u] and v!=fa[u])
{
DFS2(v,v);
}
}
}//【模板】树链剖分
vector<int>rec[N];//差分数组
void RAdd(int l,int r,int c)
{
rec[l].push_back(c);
rec[r+1].push_back(-c);
//封装成函数了呢QωQ
}
void PathAdd(int u,int v,int c)
{
while(top[u]!=top[v])
{
if (deep[top[u]]<deep[top[v]]) swap(u,v);
RAdd(DFSid[top[u]],DFSid[u],c);
//只要不断的区间加值就珂以了
u=fa[top[u]];
}
if (deep[u]>deep[v]) swap(u,v);
RAdd(DFSid[u],DFSid[v],c);
}//路径修改
int n,q;
void Input()
{
for(int i=1;i<n;++i)
{
int u,v;
scanf("%d%d",&u,&v);
Add(u,v,1);
}
}
int ans[N];
int sign(int x)//求x符号部分
{
return (x>0)?1:-1;
//是珂以x/abs(x)的,但是。。。这样不是更快么。。。
}
void Soviet()
{
DFS1(1,-1);
DFS2(1,1);
T.BuildTree(1,100010,1);//别忘了build。。。
for(int i=1;i<=q;++i)
{
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
PathAdd(u,v,c);
}
for(int i=1;i<=n;++i)
{
for(int j=0;j<rec[i].size();++j)
{
int col=rec[i][j];
T.Add(abs(col),sign(col),1);
}
if (T.tree[1].x==0) ans[Ori[i]]=0;//没有颜色。。。那就只能是0了。。。
else ans[Ori[i]]=T.Query(T.tree[1].x,1);//否则就询问一下是哪种颜色
//记得是ans[Ori[i]]=...
}
for(int i=1;i<=n;++i)
{
printf("%d\n",ans[i]);
}
}
void InitAll()
{
G.clear();
for(int i=0;i<N;++i)
{
rec[i].clear();
}
cnt=0;
memset(DFSid,0,sizeof(DFSid));
memset(Ori,0,sizeof(Ori));
memset(ans,0,sizeof(ans));
memset(top,0,sizeof(top));
memset(deep,0,sizeof(deep));
memset(son,0,sizeof(son));
memset(size,0,sizeof(size));
memset(fa,0,sizeof(fa));
}
void IsMyWife()
{
if (0)
{
freopen("","r",stdin);
freopen("","w",stdout);
}
while(~scanf("%d%d",&n,&q))
{
if (n==0 and q==0) break;
InitAll();
Input();
Soviet();
}
}
};
int main()
{
Flandle_Scarlet::IsMyWife();
return 0;
}