向前走莫回头❤

【bzoj 3083】遥远的国度(树链剖分+线段树)

3083: 遥远的国度

Time Limit: 10 Sec  Memory Limit: 1280 MB
Submit: 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

3 7
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

1
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

[Submit][Status][Discuss]


【树链剖分,建线段树存储】
【与普通树链剖分不同的是,也要像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;
}   


posted @ 2016-05-18 18:40  lris0-0  阅读(64)  评论(0编辑  收藏  举报
过去的终会化为美满的财富~o( =∩ω∩= )m