bzoj1095: [ZJOI2007]Hide 捉迷藏(动态点分治+树上ST表)

本文详细介绍了如何使用动态点分治解决复杂的数据结构问题,并通过具体题目实例进行讲解。包括如何构建父子关系、维护堆结构以及更新全局最优解等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门
这道题跟我之前做的Qtree4几乎是一样的。
都是神题。
其实我很想膜一下出题人的。
太强了。

详细解法还是看我的博客吧。
再写要写死人了。
Qtree4博客
不会动态点分治的话还是一定要看我这篇博客的(毕竟我写的很辛苦。)

这道题与Qtree4唯一的不同就是边权为1。
每次dep+1就好。

代码实现(Qtree4代码改的):

#include<set>
#include<map>
#include<ctime>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define inf 1000000000
#define mod 1000000007
#define pa pair<int,int>
#define ll long long 
using namespace std;
int bin[20],Log[200005];//bin[i]表示2的i次方,Log就是log
int n,m,G,cnt,dfn,sum,tott;
int tot[100005],f[100005],dep[100005];// f[i]数组表示去掉点i之后剩下所有子树家族最大的子树的家族数目 
//dep表示到一号节点的距离 
int mn[18][200005],ys[100005],fa[100005];//mn[i][j]表示dfs序中从j到j+2^i-1的最小深度的点 
bool v[100005],col[100005];//col表示当前这个点的颜色,1为黑色,0为白色 
struct node{
    int x,y,next;
}a[200005];int len,last[100005];
void ins(int x,int y) {
    len++;
    a[len].x=x;a[len].y=y;
    a[len].next=last[x];last[x]=len;
}
/*
做法。
对于每个节点我们维护两个堆(堆的堆顶最大)
每个节点的第一个堆维护所有子树内的节点到自己父亲节点的距离
第二个堆维护所有子节点第一个堆的堆顶(最大值)
那么相对于每一个节点来说,第二个堆的最大值加次大值就是子树内经过这个节点的最长链!!
全局维护一个堆,记录所有节点第二个堆的最大值和次大值的和。堆顶(最大值)就是答案 
*/ 
/*
堆的操作;
push(x),把x加进堆里
pop(),删除堆顶
top(),询问堆顶
size(),堆里有多少个数 
*/ 
struct heap{
    priority_queue<int> A,B;//A堆记录全部的状态,B堆记录没用的状态 
    void push(int x){  //加进一个目前有用的状态(将来可能没用了) 
        A.push(x);
    }
    void erase(int x){ //更好的理解为删除这个状态 
        B.push(x);
    }
    void pop(){  //删除堆顶 
        while(B.size()&&A.top()==B.top())  //首先要把没用的状态给删除了 
            A.pop(),B.pop();
        A.pop();  //去掉没用的状态后删除堆顶 
    }
    int top(){  //取堆顶 
        while(B.size()&&A.top()==B.top())// 删除没用的状态 
            A.pop(),B.pop();
        if(!A.size())return 0;  
        return A.top();
    }
    int size(){  //全部的状态数-没用的状态数=有用的状态数 
        return A.size()-B.size();
    }
    int stop(){  //求堆里的次大值 
        if(size()<2)return 0; //如果有用的状态数小于2说明没有次大值 
        int x=top();pop(); //先把堆顶删除 
        int y=top();push(x);//删除原来的堆顶之后剩下的堆顶就是原来的次大值!得到次大值之后再把原来的堆顶加回去 
        return y;
    }
}A,B[110000],C[110000];//C数组表示每个节点的第一个堆,B数组表示第二个堆,A表示全局的堆 
void dfs(int x,int fa) //求出dfs序和dep数组 
{
    mn[0][++dfn]=dep[x];
    ys[x]=dfn;
    for(int k=last[x];k;k=a[k].next) {
        int y=a[k].y;
        if(y!=fa)
        {
            dep[y]=dep[x]+1;
            dfs(y,x);
            mn[0][++dfn]=dep[x];
        }
    }
}
/*
getrt
找到树的重心并存在G里。
树的重心是一个点
删掉树的重心之后所剩子树家族最大的最小
*/
void getrt(int x,int fa)
{
    tot[x]=1;f[x]=0;
    for(int k=last[x];k;k=a[k].next) {
        int y=a[k].y;
        if(y!=fa&&v[y]==false)
        {
            getrt(y,x);
            tot[x]+=tot[y];
            f[x]=max(f[x],tot[y]);
        }
    }
    f[x]=max(f[x],sum-tot[x]); // 把下面的子树问完以后还有以父亲节点为根的那棵子树 
    if(f[x]<f[G]) // G记录的就是当前子树的重心 
        G=x; 
}
//按照新的树去建父子关系 
void divi(int x)
{
    v[x]=true;
    for(int k=last[x];k;k=a[k].next) {
        int y=a[k].y;
        if(v[y]==false)
        {
            sum=tot[y];G=0;   
            getrt(y,x); //找出这棵子树的重心 
            fa[G]=x;divi(G); //下一层的重心链接上一层的重心,然后继续去建新的树 
        }
    }
}
//求x到y之间深度最小的点的深度 
int rmq(int x,int y)
{
    x=ys[x];y=ys[y];
    if(x>y)
        swap(x,y);
    int t=Log[y-x+1];
    return min(mn[t][x],mn[t][y-bin[t]+1]);//ST表求解公式 
}

int dis(int x,int y) //原树上x到y的距离 
{
    return dep[x]+dep[y]-2*rmq(x,y);
}
//每个节点的堆都是维护下面的节点。
//那么改变一个节点的颜色就只会对它上面的节点有影响。 
//把v节点关上,v对u节点有影响。 
void turn_off(int u,int v)
{
    if(u==v)
    {
        B[u].push(0);
        if(B[u].size()==2)A.push(B[u].top());
    }
    if(!fa[u])return;
    int f=fa[u],D=dis(f,v),tmp=C[u].top();
    C[u].push(D);//把v节点改为黑色,那么这个距离一定是有用的。 
    if(D>tmp) //如果现在的距离优于原来的最大距离。那么有可能对我的答案有影响。 
    {
        int mx=B[f].top()+B[f].stop(),size=B[f].size();//mx记录原来的答案 
        if(tmp)B[f].erase(tmp);//tmp已经不是堆顶了,不能被记录在B[f]里。要删除 
        B[f].push(D);          //D成为了堆顶 ,要被记录在B[f]里。 
        int now=B[f].top()+B[f].stop(); //当前的答案 
        if(now>mx)  //如果当前答案优于原来的答案 
        {
            if(size>=2)A.erase(mx);  //size>=2说明你有最大值和次大值,mx已经不是最优答案了,要把他删除 
            if(B[f].size()>=2)A.push(now);//now是当前最优答案,要记录进全局堆 
        }
    }
    turn_off(f,v);// 往上更新 
}
//把v节点打开,v对于u有影响 
void turn_on(int u,int v)//跟turn_off思想差不多 
{
    if(u==v)
    {
        if(B[u].size()==2)A.erase(B[u].top());
        B[u].erase(0);    
    }
    if(!fa[u])return;
    int f=fa[u],D=dis(f,v),tmp=C[u].top();
    C[u].erase(D);//D肯定没有用了,因为v变成了白色 
    if(D==tmp)//如果我等于原来的堆顶,那么有可能删掉的就是堆顶(堆顶有可能有多个重复),那么就有可能对我的答案有影响 
    {
        int mx=B[f].top()+B[f].stop(),size=B[f].size();
        B[f].erase(D);//很明显D没用了要删除。 
        if(C[u].top())B[f].push(C[u].top());//如果你的堆顶不为零,那么我要记录一下。 
        int now=B[f].top()+B[f].stop();//现在的答案 
        if(now<mx)  //原来的答案经过更新之后变小了,说明改变v是对f的答案有影响的,那么我要更新全局 
        {
            if(size>=2)A.erase(mx); // 不解释了 
            if(B[f].size()>=2)A.push(now);
        }
    }
    turn_on(f,v);
}
int main()
{
    bin[0]=1;for(int i=1;i<20;i++)bin[i]=bin[i-1]<<1;
    Log[0]=-1;for(int i=1;i<=200000;i++)Log[i]=Log[i>>1]+1;
    scanf("%d",&n);
    len=0;memset(last,0,sizeof(last));
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    dfs(1,0);
    // mn[i][j]表示j到j+2^i-1最小深度的点
    // ST表模板 
    for(int i=1;i<=Log[dfn];i++)
        for(int j=1;j<=dfn;j++)
            if(j+bin[i]-1<=dfn)
                mn[i][j]=min(mn[i-1][j],mn[i-1][j+bin[i-1]]);
    G=0;f[0]=inf;sum=n;
    getrt(1,0);
    fa[G]=0;divi(G);
    for(int i=1;i<=n;i++)col[i]=1;//col[i]=1时表示该节点为关着的,=0时为开着的。
    for(int i=1;i<=n;i++)
    {
        turn_off(i,i);
        tott++;//tott表示关灯房间数量数量 
    }
    char ch[2];
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",ch+1);
        if(ch[1]=='G')
        {
            if(tott<=1)printf("%d\n",tott-1);
            else printf("%d\n",A.top());
        }
        else
        {
            int x;scanf("%d",&x);
            if(col[x])turn_on(x,x),tott--;
            else turn_off(x,x),tott++;
            col[x]^=1;
        }
    }
    return 0;
}

这两题都神。
我写那么详细就是为了让别人更容易学。
因为我学的时候大神只有几句解释我这种蒟蒻理解不了啊![苦逼]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值