【bzoj 3083】遥远的国度(树链剖分+线段树)
3083: 遥远的国度
Time Limit: 10 Sec Memory Limit: 1280 MBSubmit: 2378 Solved: 586
[Submit][Status][Discuss]
Description
描述
zcwwzdjn在追杀十分sb的zhx,而zhx逃入了一个遥远的国度。当zcwwzdjn准备进入遥远的国度继续追杀时,守护神RapiD阻拦了zcwwzdjn的去路,他需要zcwwzdjn完成任务后才能进入遥远的国度继续追杀。
问题是这样的:遥远的国度有n个城市,这些城市之间由一些路连接且这些城市构成了一颗树。这个国度有一个首都,我们可以把这个首都看做整棵树的根,但遥远的国度比较奇怪,首都是随时有可能变为另外一个城市的。遥远的国度的每个城市有一个防御值,有些时候RapiD会使得某两个城市之间的路径上的所有城市的防御值都变为某个值。RapiD想知道在某个时候,如果把首都看做整棵树的根的话,那么以某个城市为根的子树的所有城市的防御值最小是多少。由于RapiD无法解决这个问题,所以他拦住了zcwwzdjn希望他能帮忙。但zcwwzdjn还要追杀sb的zhx,所以这个重大的问题就被转交到了你的手上。
Input
第1行两个整数n m,代表城市个数和操作数。
第2行至第n行,每行两个整数 u v,代表城市u和城市v之间有一条路。
第n+1行,有n个整数,代表所有点的初始防御值。
第n+2行一个整数 id,代表初始的首都为id。
第n+3行至第n+m+2行,首先有一个整数opt,如果opt=1,接下来有一个整数id,代表把首都修改为id;如果opt=2,接下来有三个整数p1 p2 v,代表将p1 p2路径上的所有城市的防御值修改为v;如果opt=3,接下来有一个整数 id,代表询问以城市id为根的子树中的最小防御值。
Output
对于每个opt=3的操作,输出一行代表对应子树的最小点权值。
Sample Input
1 2
1 3
1 2 3
1
3 1
2 1 1 6
3 1
2 2 2 5
3 1
2 3 3 4
3 1
Sample Output
2
3
4
提示
对于20%的数据,n<=1000 m<=1000。
对于另外10%的数据,n<=100000,m<=100000,保证修改为单点修改。
对于另外10%的数据,n<=100000,m<=100000,保证树为一条链。
对于另外10%的数据,n<=100000,m<=100000,没有修改首都的操作。
对于100%的数据,n<=100000,m<=100000,0<所有权值<=2^31。
HINT
Source
【树链剖分,建线段树存储】
【与普通树链剖分不同的是,也要像dfs序那样存一个in和out,因为in和trn1是一样的,存一个就行了。这样在查询时会提高效率(不需要写lca了)。因为还有换根操作,但实际上,不需要splay,只需要在查询的时候讨论即可】
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[200010],next[200010],p[200010],tot;
int val[100010],sum[500010];
int fa[100010],son[100010],dep[100010],size[100010];
int top[100010],trn2[100010],in[100010],out[100010],tip;
bool delta[500010];
int n,m,root,lastroot,i;
inline void add(int x,int y)
{
tot++; a[tot]=y; next[tot]=p[x]; p[x]=tot;
tot++; a[tot]=x; next[tot]=p[y]; p[y]=tot;
return;
}
inline void clear()
{
memset(son,-1,sizeof(son));
memset(delta,true,sizeof(delta));
return;
}
inline void dfs1(int now,int father,int h)
{
fa[now]=father; dep[now]=h; size[now]=1;
int u=p[now];
while (u!=0)
{
int v=a[u];
if (v!=father)
{
dfs1(v,now,h+1);
size[now]+=size[v];
if (son[now]==-1||size[v]>size[son[now]])
son[now]=v;
}
u=next[u];
}
return;
}
inline void dfs2(int now,int father)
{
top[now]=father;
in[now]=++tip; trn2[tip]=now;//in、out含义同dfs序里的
if (son[now]==-1) {out[now]=tip; return;}//因为没有重儿子就退出所以此时没有更新out值,要在退出前更新一下
dfs2(son[now],father);
int u=p[now];
while (u!=0)
{
int v=a[u];
if (v!=fa[now]&&v!=son[now])
dfs2(v,v);
u=next[u];
}
out[now]=tip;
return;
}//树链剖分部分
inline void updata(int now)
{
sum[now]=min(sum[(now<<1)],sum[(now<<1)|1]);//线段树维护的是这一段区间里的最小值
return;
}
inline void pushdata(int now)
{
while (delta[now]==false)
{
sum[(now<<1)]=sum[now];
delta[(now<<1)]=false;
sum[(now<<1)|1]=sum[now];
delta[(now<<1)|1]=false;
delta[now]=true;
}//由于线段树维护最小值,直接将每一个sum更改成当前值即可
return;
}
inline void build(int now,int l,int r)
{
if (l==r)
{sum[now]=val[trn2[l]]; return;}
int mid=(l+r)>>1;
build((now<<1),l,mid);
build((now<<1)|1,mid+1,r);
updata(now);
}
inline void Change(int now,int al,int ar,int l,int r,int v)
{
if (al<=l&&r<=ar)
{
sum[now]=v;//与pushdata原因相同
delta[now]=false;
return;
}
int mid=(l+r)>>1;
pushdata(now);
if (al<=mid) Change((now<<1),al,ar,l,mid,v);
if (ar>mid) Change((now<<1)|1,al,ar,mid+1,r,v);
updata(now);
return;
}
inline int ask(int now,int al,int ar,int l,int r)
{
if (al<=l&&r<=ar)
return sum[now];
int mid=(l+r)>>1,ans=2147283647;//因为数据很大,ans要尽量附一个大值
pushdata(now);
if (al<=mid) ans=min(ans,ask((now<<1),al,ar,l,mid));
if (ar>mid) ans=min(ans,ask((now<<1)|1,al,ar,mid+1,r));
return ans;
}
inline void change(int x,int y,int z)
{
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
Change(1,in[top[x]],in[x],1,n,z);
x=fa[top[x]];
}
if (dep[x]>dep[y]) swap(x,y);
Change(1,in[x],in[y],1,n,z);
return;
}//树链剖分中真正改变的区间是并不是x、y,而是先将它们蹦到同一深度,再一起更改,在这之前,改的是深度较深的一个(同lca)
inline bool check(int root,int r)
{
if (in[r]<=in[root]&&in[root]<=out[r])//即根的in、out是否在r所控制的区间里
return true;
return false;
}//判断根在不在当前查询的点控制的范围内
inline int found(int root,int r)
{
int u=p[r];
while (u!=0)
{
int v=a[u];
if (check(root,v)) return v;
u=next[u];
}
} //找根是在r的哪一棵子树里
int main()
{
scanf("%d%d",&n,&m);
for (i=1;i<n;++i)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
}
for (i=1;i<=n;++i)
scanf("%d",&val[i]);
scanf("%d",&root);
clear();
dfs1(1,0,1);
dfs2(1,1);
build(1,1,n);
for (i=1;i<=m;++i)
{
int op;
scanf("%d",&op);
if (op==1) {scanf("%d",&root); continue;}
if (op==2)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
change(x,y,z);
continue;
}
if (op==3)
{
int r;
scanf("%d",&r);
if (r==root) {printf("%d\n",sum[1]); continue;}//若查询的元素是根,那么答案就是整棵树的最小值
if (check(root,r))
{
int k=found(root,r);
int ans;
ans=ask(1,out[k]+1,n,1,n);
ans=min(ans,ask(1,1,in[k]-1,1,n));
printf("%d\n",ans);
}//r的区间里包含根的情况中,查询时要扣除那一棵子树,即分别查询那一棵子树之前的区间和那一棵子树之后的区间
else printf("%d\n",ask(1,in[r],out[r],1,n));//若根不在待查询区间,则直接查询即可
}
}
return 0;
}