题目链接:E. Treeland and Viruses
题目大意:有一棵有(n)个节点的树,(q)次询问(询问互相独立),每次给定(k_i)个颜色,每个颜色有一个起始点(v_j)和移动速度(s_j),每一个颜色在每一次操作中会使它周围没有被染色的连通块上与它的距离不超过(s_j)的点全部染为这一个颜色,每一轮中,颜色从(1)到(k_i)依次开始操作,一直到所有点全部被染色为止,再询问(m_i)个关键点的颜色。
题解:这一道题一看就像是建虚树,先考虑把虚树建出来,由于某些颜色会被阻断,所以不能够直接用树上路径长度求出,所以每一个点只能更新与它相邻的点,所以,先用孩子更新父亲,然后再用父亲更新孩子,这样做两遍之后就可以得出答案。至于依次染色的限制,可以直接用优先级解决掉(以到达时间为第一关键字,颜色种类为第二关键词)。思路还是挺简洁明了的。
时间复杂度(O(n ext{log}n)),不过我的代码应该带上了一个很大的常数,如果用树链剖分求LCA应该会快不少。
感觉自己的语文水平不太够啊,如果真的看不懂就看代码吧,代码应该也不难理解。
下面是代码:
#include <vector>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
void read(int &a){
a=0;
char c=getchar();
while(c<'0'||c>'9'){
c=getchar();
}
while(c>='0'&&c<='9'){
a=(a<<1)+(a<<3)+(c^48);
c=getchar();
}
}
const int Maxn=200000;
int head[Maxn+5],arrive[Maxn<<1|5],nxt[Maxn<<1|5],tot;
void add_edge(int from,int to){
arrive[++tot]=to;
nxt[tot]=head[from];
head[from]=tot;
}
int n,q;
int fa[18][Maxn+5];
int dep[Maxn+5];
int dfn[Maxn+5],out[Maxn+5],dfn_tot;
void init_dfs(int u){
dfn[u]=++dfn_tot;
dep[u]=dep[fa[0][u]]+1;
for(int i=1;fa[i-1][fa[i-1][u]];i++){
fa[i][u]=fa[i-1][fa[i-1][u]];
}
for(int i=head[u];i;i=nxt[i]){
int v=arrive[i];
if(v==fa[0][u]){
continue;
}
fa[0][v]=u;
init_dfs(v);
}
out[u]=dfn_tot;
}
int find_lca(int u,int v){
if(dep[u]<dep[v]){
swap(u,v);
}
for(int i=17;i>=0;i--){
if(dep[fa[i][u]]>=dep[v]){
u=fa[i][u];
}
}
if(u==v){
return u;
}
for(int i=17;i>=0;i--){
if(fa[i][u]!=fa[i][v]){
u=fa[i][u];
v=fa[i][v];
}
}
return fa[0][u];
}
int find_dis(int u,int v){
return dep[u]+dep[v]-(dep[find_lca(u,v)]<<1);
}//正常的倍增求LCA,树上距离等等
int sp[Maxn+5],type[Maxn+5];
int f[Maxn+5];
int lim[Maxn<<2|5],lim_len;
int pa[Maxn+5];
int imp[Maxn+5];
int st[Maxn+5],st_top;
int get_time(int u,int v){
return (find_dis(u,v)+sp[type[u]]-1)/sp[type[u]];
}//求出一个颜色的起始点到另一个点所需时间
int merge(int u,int a,int b){
if(u==0){
return 0;
}
if(a==0){
return b;
}
if(b==0){
return a;
}
int dis_a=get_time(a,u),dis_b=get_time(b,u);
if(dis_a==dis_b){
return type[a]<type[b]?a:b;
}
return dis_a<dis_b?a:b;//双关键字
}
bool cmp(int p,int q){
return dfn[p]<dfn[q];
}
int main(){
read(n);
int u,v;
for(int i=1;i<n;i++){
read(u),read(v);
add_edge(u,v);
add_edge(v,u);
}
init_dfs(1);
read(q);
int k,m;
for(int i=1;i<=q;i++){
read(k),read(m);
lim_len=0;
for(int j=1;j<=k;j++){
read(u),read(v);
type[u]=j;
sp[j]=v;
f[u]=u;
lim[++lim_len]=u;
}
for(int j=1;j<=m;j++){
read(u);
lim[++lim_len]=u;
imp[j]=u;
}
sort(lim+1,lim+1+lim_len,cmp);
for(int j=lim_len-1;j>0;j--){
lim[++lim_len]=find_lca(lim[j],lim[j+1]);
}
sort(lim+1,lim+1+lim_len,cmp);
lim_len=unique(lim+1,lim+1+lim_len)-lim-1;
st_top=0;
for(int j=1;j<=lim_len;j++){
while(st_top&&out[st[st_top]]<dfn[lim[j]]){
st_top--;
}
if(st_top){
pa[lim[j]]=st[st_top];
}
st[++st_top]=lim[j];
}//普通的建虚树过程
for(int j=lim_len;j>0;j--){
f[pa[lim[j]]]=merge(pa[lim[j]],f[pa[lim[j]]],f[lim[j]]);
}//用孩子去更新父亲(往祖先染)
for(int j=1;j<=lim_len;j++){
f[lim[j]]=merge(lim[j],f[lim[j]],f[pa[lim[j]]]);
}//用父亲来更新孩子(往孩子染)
for(int j=1;j<=m;j++){
printf("%d ",type[f[imp[j]]]);
}
puts("");
for(int j=1;j<=lim_len;j++){
type[lim[j]]=0;
f[lim[j]]=0;
pa[lim[j]]=0;
}//一定要清零清零清零!!!!
}
return 0;
}