[ZJOI2007]捉迷藏

前言

又是一道动点分
真是越来越套路

题面

详细题面请
概要 :
一棵树,一开始,所有点都为白色,若干次修改。每一次选择一个点,改变其颜色(白变黑,黑变白)。若干次询问,每一次问最远的两个白点的距离。

sol

这道题据说有两种解法,点分治与括号序列

点分治解法自然,易于理解
首先一点就是,如何用STL优先队列删除某一个指定元素。
一般会在迪杰斯特拉求最短路中用到。这时,一般搞一个数组记一下就可以了。因为被更新后的元素会更先出堆。

但这道题出堆后会继续入堆,而且堆的数量众多,每一个堆配一个数组开不下。这时,我们就给每一个堆配一个删除堆。当堆的某一元素被删除后,由于无法快速找到,实际上我们可以把它丢到一个堆里。可以证明,当这个元素到达堆顶时,删除堆里的元素也一定在堆顶。

证明如下:
这样相当于维护了一个堆中堆。即,这个堆为一个集合S,这些被删除的元素为集合T;集合T单独在外面又存了一个副本。当集合T中最大元素不为堆顶,没有任何影响。集合T中要出堆的元素一定是集合S中的最大值,也是集合T中的最大值。
这样是一个常用的套路。



上面算是本道题的一个影子。我们还没有谈思路。本题之所以可以点分治是因为其可以通过划分成几个子树来讨论。比如,先算这一条路径过重心的最大值。也就是说这条路径跨了两棵子树。这样答案的贡献也一定是先算出每一棵子树内一个离重心最远的白点(定义关了灯为白点),然后选最大的两个。这个答案不一定最优,但我们发现另一个最优答案只可能出现在提供了离重心最远白点的子树内。(证明不难,但纯靠文字好像表达的有点吃力)。这个时候,就可以跳点分树。

如果这样,那么上述相当于是解释了查询。那么,依照查询一般可以看出修改。一个点的修改只要考虑他到每一层重心的贡献(即到每一层重心的距离),如果它不存在于某个子树中是不需要考虑对该子树的重心的贡献的。这其实也是所有点分治的题目所共要有的性质。
解释了这些后点分治是可解的。由于题目中在动态的维护与修改最大值,堆会要代替之前的点分治的一些数组,这也是为什么开头就解释堆。

每一级子树对上一级子树会有影响,这也是为什么在许多点分治的题中要建一个tofa的数组。如果是这一道题,首先要建的就是一个可备选答案堆,也就是每一个子树离自己最远的白点的距离建成一个堆,这样可以直接求答案。

但又注意到这道题和幻想乡战略游戏有点不一样的,当在考虑一个子树时,不需要考虑这棵子树以外的树对其的贡献。所以不需要从父亲那获得兄弟子树的信息,同时也不要从爷爷子树那减去父亲子树的贡献得到叔叔子树们贡献。这样,tofa这个数组也许没有了必要.但仅靠一个可备选答案堆是不能相互递推的。所以对于每一棵子树的重心那,维护的还要有该子树内所有点到他的距离(其实这一个在幻想乡战略游戏里又有一个类似的数组)

其实类比一下两道题的维护节点信息的方法有,
一个是求和算贡献,一个是取max算贡献,所以一个用数组,一个用堆。
一个是考虑一个关建点时,要算全局的代价,一个由于性质保证,考虑一个关键点时,只要考虑这个范围内子树的代价,所以一个有tofa,一个没有
一个直接利用全局的数组进行求和,一个为了保证两条路径不出现在同一个子树,所以一个没有堆每个子树单独维护他们的贡献,一个需要。

最后一个细节,自己到自己也应该算成一条路径。所以要往堆内要放一个0;
最后一个优化,堆内只要维护一个整数,不需要考虑它是有哪一个点提供的.

小贴士

如果想要两个结构体互相识别,可以把其中一个改成namespace,namespace可以分成两次定义

代码

#include<bits/stdc++.h>
using namespace std;
const int _ = 1e5+1e3,INF = 2e9;
inline char gc(){
    static char buf[1<<6],*p1=buf,*p2=buf;
    return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<6,stdin),p1==p2)?EOF:*p1++;
}
template <class T>
inline void read(T&data){
    data=0;
    register char ch=0;
    while(ch<'0'||ch>'9')ch=gc();
    while(ch<='9'&&ch>='0'){
        data=(data<<3)+(data<<1)+(ch&15);
        ch=gc();
    }
    return;
}
namespace stringio{char Getchar(){register char ch=0;while(ch<'A'||ch>'Z')ch=gc();return ch;}}
using namespace stringio;

int n,m;
bool used[_];//零表示灭
struct node{
    int dist,from;
    bool operator < (const node fr) const {
        if(dist!=fr.dist)return dist<fr.dist;
        return from<fr.from;
    }
};
bool operator == (node qw,node wq){
    if((qw.dist==wq.dist)&&(qw.from==wq.from))return 1;
    return 0;
}
struct heap{
    priority_queue<node>Q,del;
    void Delete(register node x){del.push(x);return;}
    void Push(register node x){Q.push(x);return;}
    void Clear(){while(!del.empty()&&del.top()==Q.top())Q.pop(),del.pop();return;}
    int Size(){Clear();return Q.size()-del.size();}
    node Get_fir(){Clear();if(Q.empty())return (node){-1,-1};else return Q.top();}
    node Get_sec(){
        if(Size()<2)return (node){-1,-1};
        register node x1 = Get_fir();Q.pop();
        register node x2 = Get_fir();Q.push(x1);
        return x2;
    }
    bool Empty(){Clear();return Q.empty();}
    int Get(){
        if(Size()<2)return -1;
        register int x1 = Get_fir().dist,x2 = Get_sec().dist;
        return (x2 + x1);
    }
}c[_],b[_],test;//b是该子树内所有点到其父亲的集合,c是每一个儿子的b堆的堆顶
//自己算,作自己的子树.
namespace Tree{
    int head[_],cnt,par[_],root;
    struct edge{
        int nt,to;
    }e[_];
    void add(register int a,register int b){e[++cnt].to=b,e[cnt].nt=head[a],head[a]=cnt;par[b]=a;return;}
}
class orginal_tree{
private:
    int head[_];
    struct edge{
        int nt,to;
    }e[_<<1];
    bool vis[_];
    int size[_],MX,all,lg[_<<1],st[18][_<<1],cnt,dfn[_],dis[_],fdfn[_],app[_],tot,root;
    void dfs(int now,int fa){
        dfn[now]=++cnt,fdfn[cnt]=now;st[0][++tot]=cnt,app[now]=tot;
        for(register int i=head[now];i;i=e[i].nt){
            if(e[i].to==fa)continue;
            dis[e[i].to]=dis[now]+1;
            dfs(e[i].to,now);
            st[0][++tot]=dfn[now];
        }
    }
    int get_lca(register int u,register int v){
        u=app[u],v=app[v];
        if(u<v)swap(u,v);register int LEN=lg[u-v+1];
        return fdfn[min(st[LEN][v],st[LEN][u+1-(1<<LEN)])];
    }
    void getroot(register int now,register int fa){
        size[now]=1;
        int mx=0;
        for(register int i=head[now];i;i=e[i].nt)
        {
            if(vis[e[i].to]||e[i].to==fa)continue;
            getroot(e[i].to,now);
            size[now]+=size[e[i].to];
            mx=mx<size[e[i].to]?size[e[i].to]:mx;
        }
        mx=mx<all-size[now]?all-size[now]:mx;
        if(MX>mx)MX=mx,root=now;
    }
    void p_divide(int now){
        vis[now]=1;
        for(register int i=head[now];i;i=e[i].nt){
            if(vis[e[i].to])continue;
            MX=INF,all=size[e[i].to];
            getroot(e[i].to,0);
            Tree::add(now,root);
            p_divide(root);
        }
    }
public:
    void add(register int a,register int b){e[++cnt].to=a,e[cnt].nt=head[b],head[b]=cnt;}
    void Add(){for(register int i=1,a,b;i<n;++i)read(a),read(b),add(a,b),add(b,a);}
    void init(){
        Add();
        MX=INF,all=n;
        cnt=0;dfs(1,0);getroot(1,0);
        register int us=2*n-1;
        for(register int i=2;i<=us;++i)lg[i]=lg[i>>1]+1;
        for(register int j=1;j<=17;++j)
            for(register int i=1;i<=us&&(i+(1<<(j-1)))<=us;++i)
                st[j][i]=min(st[j-1][i],st[j-1][i+(1<<(j-1))]);
        Tree::root=root;
        p_divide(root);
    }
    int getdist(register int u,register int v){
        return dis[u]+dis[v]-2*dis[get_lca(u,v)];
    }
}tr;
namespace Tree{
    void insert(register int now){
        used[now]=1;
        c[now].Push((node){0,now});
        for(register int i=now;par[i];i=par[i]){
            register int dist=tr.getdist(now,par[i]);
            register node rec=b[i].Get_fir();
            if(rec.dist<dist){
                c[par[i]].Push((node){dist,i});
                if(rec.dist!=-1)c[par[i]].Delete(rec);
            }
            b[i].Push((node){dist,i});
        }
    }
    void erase(register int now){
        used[now]=0;
        c[now].Delete((node){0,now});
        for(register int i=now;par[i];i=par[i]){
            register int dist=tr.getdist(now,par[i]);
            register node rec=b[i].Get_fir();
            if(rec.dist==dist){
                b[i].Delete((node){dist,i});
                c[par[i]].Delete((node){dist,i});
                rec=b[i].Get_fir();
                if(rec.dist!=-1)c[par[i]].Push(rec);        
            }
            else b[i].Delete((node){dist,i});
        }
    }
    void init(){
        for(register int i=1;i<=n;++i)
            insert(i)      
    }
    int query(register int now){
        register int size=c[now].Size();
        if(size>=2){
            node jj=c[now].Get_fir();
            return max(c[now].Get(),query(jj.from));
        }
        if(size==1){
            node jj=c[now].Get_fir();
            if(jj.from==now)return 0;
            return query(jj.from);
        }
        return -1;
    }
}
int main(){
    freopen("data.in","r",stdin);
    read(n);
    tr.init();
    Tree::init();
    read(m);
    while(m--){
        register  char ki=Getchar();
        register int fdfdf;
        if(ki=='G'){
            printf("%d\n",Tree::query(Tree::root));
        }
        else {
            read(fdfdf);
            used[fdfdf]?Tree::erase(fdfdf):Tree::insert(fdfdf);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/JH_2002/article/details/81349653
今日推荐