BZOJ-3083 遥远的国度 树链剖分+分类讨论 or DFS序

3083: 遥远的国度
Time Limit: 10 Sec Memory Limit: 1280 MB
Submit: 2165 Solved: 525
[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
zhonghaoxi提供

题解:
很明显的树链剖分
至于换根操作,不同于动态树问题中的换根,动态树问题的换根中,树的形态和结构发生了变化,所以需要用灵活的LCT之类的去维护,而这个题,仔细分析发现,只是名义上的根发生了变化,实际树的形态和结构并没有发生变化,所以还是可以用链剖去解决,只需要分类讨论一下即可;
操作1:记录下新的根即可
操作2:随便搞搞,求一下区间的最小即可,同T2
操作3:根据id和root分类讨论;
①root==id,询问整棵树
②fa[root]==id,询问除了root所在子树以外的整棵树
③root在id的子树里,且距离大于1,询问除了root的除了其祖先是id的儿子的祖先的子树以外的整棵树
④root不在id的子树里,询问id的子树

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int read()
{
    int x=0,f=1; char ch=getchar();
    while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
    while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
#define maxn 200010
int n,m,sd;struct data{int next,to;}edge[maxn*2];
int head[maxn],cnt;
void add(int u,int v){cnt++;edge[cnt].next=head[u];head[u]=cnt;edge[cnt].to=v;}
void insert(int u,int v){add(u,v);add(v,u);}
//--------------------------------------------------------------------------------------
int deep[maxn],size[maxn],son[maxn],pl[maxn],sz,pr[maxn],pre[maxn],top[maxn],fa[maxn],father[maxn][25];
void dfs_1(int now)
{
    size[now]=1;
    for (int i=head[now]; i; i=edge[i].next)
        if (edge[i].to!=fa[now])
            {
                fa[edge[i].to]=now;
                deep[edge[i].to]=deep[now]+1;
                dfs_1(edge[i].to);
                if (size[son[now]]<size[edge[i].to]) son[now]=edge[i].to;
                size[now]+=size[edge[i].to];
            }
}
void dfs_2(int now,int chain)
{
    pl[now]=++sz; pre[sz]=now; top[now]=chain;
    if (son[now]) dfs_2(son[now],chain);
    for (int i=head[now]; i; i=edge[i].next)
        if (edge[i].to!=son[now] && edge[i].to!=fa[now])
            dfs_2(edge[i].to,edge[i].to);
    pr[now]=sz;
}
int LCA(int x,int y)
{
    int dx=deep[x],dy=deep[y];
    if (dx<dy) swap(dx,dy); int dd=dx-dy;
    for (int i=0; i<=20; i++) if (dd&(1<<i)) x=father[x][i];
    for (int i=20; i>=1; i++) 
        if (father[x][i]!=father[y][i])
            x=father[x][i],y=father[y][i];
    if (x==y) return x; else return father[x][0];   
}
//--------------------------------------------------------------------------------------
int tree[maxn<<2],del[maxn<<2];
inline void update(int now)
{if (tree[now<<1]<tree[now<<1|1]) tree[now]=tree[now<<1];else tree[now]=tree[now<<1|1];}
inline void pushdown(int now,int l,int r)
{
    if (!del[now]) return;
    int mid=(l+r)>>1; int dd=del[now];
    del[now]=0; del[now<<1]=dd; del[now<<1|1]=dd;
    tree[now<<1]=dd; tree[now<<1|1]=dd;
}
void point_change(int now,int l,int r,int loc,int val)
{
    pushdown(now,l,r);
    if (l==r) {tree[now]+=val;return;}
    int mid=(l+r)>>1;
    if (loc<=mid) point_change(now<<1,l,mid,loc,val);
    else point_change(now<<1|1,mid+1,r,loc,val);
    update(now);
}
void segment_change(int now,int l,int r,int L,int R,int val)
{
    pushdown(now,l,r);
    if (L<=l && R>=r) {tree[now]=val;del[now]=val;return;}
    int mid=(l+r)>>1;
    if (L<=mid) segment_change(now<<1,l,mid,L,R,val);
    if (R>mid) segment_change(now<<1|1,mid+1,r,L,R,val);
    update(now);
}
int segment_ask(int now,int l,int r,int L,int R)
{
    pushdown(now,l,r);
    if (L<=l && R>=r) return tree[now];
    int mid=(l+r)>>1; int ans=0x7fffffff;
    if (L<=mid) ans=min(ans,segment_ask(now<<1,l,mid,L,R));
    if (R>mid) ans=min(ans,segment_ask(now<<1|1,mid+1,r,L,R));
    return ans;
}
//--------------------------------------------------------------------------------------
void solve1(int id)
{
    sd=id;  
}
void solve2(int p1,int p2,int val)
{
    while (top[p1]!=top[p2])
        {
            int tp1=top[p1],tp2=top[p2];
            if (deep[tp1]<deep[tp2]) swap(tp1,tp2),swap(p1,p2);
            segment_change(1,1,n,pl[tp1],pl[p1],val);
            p1=fa[p1]; tp1=top[p1];
        }
    if (deep[p1]>deep[p2]) swap(p1,p2);
    segment_change(1,1,n,pl[p1],pl[p2],val);
}
void solve3(int id)
{
    if(id==sd) printf("%d\n",segment_ask(1,1,n,1,n));
    else if(fa[sd]==id)
            printf("%d\n",min(segment_ask(1,1,n,1,pl[sd]-1),pr[sd]==n?0x7fffffff:segment_ask(1,1,n,pr[sd]+1,n)));
         else if(pl[sd]>=pl[id]&&pl[sd]<=pr[id])
            {
                int U=sd;
                while(fa[top[U]]!=id&&top[U]!=top[id]) U=fa[top[U]];
                if(fa[top[U]]!=id)
                  U=pre[pl[id]+1];
                else
                  U=top[U];
                printf("%d\n",min(segment_ask(1,1,n,1,pl[U]-1),pr[U]==n?0x7fffffff:segment_ask(1,1,n,pr[U]+1,n)));
            }
         else
             printf("%d\n",segment_ask(1,1,n,pl[id],pr[id]));
}
//--------------------------------------------------------------------------------------
int main()
{
    n=read(),m=read();
    for (int u,v,i=1; i<=n-1; i++)
        u=read(),v=read(),insert(u,v);
    dfs_1(1);  dfs_2(1,1);
    for (int pro,i=1; i<=n; i++)
        pro=read(),point_change(1,1,n,pl[i],pro);
    sd=read();
    for (int i=1; i<=m; i++)
        {
            int opt=read();int id,p1,p2,v;
            switch (opt)
                {
                    case 1: id=read(); solve1(id);break;
                    case 2: p1=read(),p2=read(),v=read(); solve2(p1,p2,v);break;
                    case 3: id=read(); solve3(id);break;
                }
        }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值