zoukankan      html  css  js  c++  java
  • 最小行走距离(dfs+虚树)

    好吧,感觉标题的名字取得有一点奇葩呢!源于我的高度概括

    所以再做一些具体放阐述,这个问题大概意思是:在树上指定了一些点,你可以从树上的任意点出发,求走完所有指定点的最小行走距离(可存在一条边重复走,走几次这条边的权值就加几次)。

    这个问题呢,我是在训练考试中碰到的,但是题目和要求做过一些小修改,为了更好的理解这种思想,我搜索到了这样一道最原始的题。

    [SDOI2015]寻宝游戏

    如何保证距离最短?我们可以做出这样的分析:

    对于一个点,我们可以向子树走,也可以向父亲走。向父亲走肯定没有向子树走更优,因为你迟早要走一遍子树,先走父亲不过是又多加了一段重复走的路。(因为还要下来)

    所以对于每一个点,我们选择先走子树再往上走->dfs序

    而为什么说是虚树呢?其实我觉得不说虚树这个概念也没什么,因为我们只需要走那些被选择的点,而没有走完整棵树。

    因为是动态的,我们考虑在当前状态下再加入一个点和删去一个点要怎么处理:

    对于一条链,每一次变化时找到这个点插入的前驱pre和后继nxt,加入的时候删除dis(pre,nxt),加上dis(pre,x) + dis(x,pre),删除同理。

    然后这个dfs序就用set维护

    #include<bits/stdc++.h>
    #define N 100003
    #define LL long long
    using namespace std;
    int read()
    {
        int x=0,f=1;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    struct EDGE{
        int to,nextt,val;
    }w[N*2];
    int vis[N],tot=0,head[N],now[N],cnt=0;
    int f[N][23],dep[N],id[N],pos[N];LL valuee[N];
    LL ans=0;
    set<int>s;
    void add(int a,int b,int c)
    {
        tot++;
        w[tot].to=b;
        w[tot].nextt=head[a];
        w[tot].val=c;
        head[a]=tot;
    }
    void dfs(int x)
    {
        id[++cnt]=x;
        pos[x]=cnt;
        for(int i=head[x];i;i=w[i].nextt)
        {
            int v=w[i].to;
            if(v==f[x][0])continue;
            f[v][0]=x;valuee[v]=valuee[x]+w[i].val;
            for(int i=1;i<=20;++i)
              f[v][i]=f[f[v][i-1]][i-1];
            dep[v]=dep[x]+1;
            dfs(v);
        }
    }
    int lca(int x,int y)
    {
        if(dep[x]<dep[y])swap(x,y);
        for(int i=20;i>=0;--i)if(dep[f[x][i]]>=dep[y])x=f[x][i];
        if(x==y)return x;
        for(int i=20;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    LL dis(int x,int y)
    {
        LL res=0;
        int LCA=lca(x,y);
        res+=valuee[x]+valuee[y]-2*valuee[LCA];
        return res;
    }
    void modify(int x)
    {
        now[x]^=1;
        if(now[x]) s.insert(pos[x]);
        else s.erase(pos[x]);
        if(s.size()<=1){ans=0;return;}//存在只有一个和没有的情况,都要直接返回,这个时候就已经不存在前驱后继了 
        set<int>::iterator pre,nxt;
        pre=s.lower_bound(pos[x]);//大于等于
        nxt=pre;
        if(now[x])nxt++;
        if(pre==s.begin())pre=--s.end();
        else pre--;
        if(nxt==s.end()) nxt=s.begin();
        LL dis1=dis(id[*pre],id[*nxt]);
        LL dis2=dis(id[*pre],x);
        LL dis3=dis(x,id[*nxt]);
        if(now[x])ans+=dis2+dis3-dis1;
        else ans-=dis2+dis3-dis1;
    }
    int main()
    {
        int n=read(),m=read();
        for(int i=1;i<n;++i)
        {
            int a=read(),b=read(),c=read();
            add(a,b,c);add(b,a,c);
        }
        dep[1]=1,dfs(1);
        for(int i=1;i<=m;++i)
        {
            int x=read();
            modify(x);
            printf("%lld
    ",ans);
        }
    } 
    BZOJ3991

    然后看一下考试时遇到的有一点小变化的题

    题面

    初音未来的巡游

    128MB / 1s ; cruise.cpp / c / pas / in / out

    【题目描述】

    Miku决定在n个地点中选择一些地点进行巡游。这n个地点由n-1条道路连接,两两之间有且仅有一条路径可以互相到达。Miku希望在这些道路中选择一些放上葱,使得Miku可以选择一种方式只经过有葱的道路而巡游完所有她选择的地点(一条道路可以被多次经过,起点任选)。

    Miku想知道她至少需要准备多少葱。由于她的巡游计划可能更改,她希望你能在更改后即时回答她的疑问。

     

    【输入格式】

    第一行两个整数n,m,表示地点数和事件数。

    第2至n行,每行两个整数x,y,表示地点x和地点y之间有一条无向道路。

    接下来一行n个0/1数,若第i个数为1则表示i号地点初始时被选,为0则表示不选。

    接下来一行m个整数,依次表示修改事件。第i个数Ai表示Ai号地点的状态发生变化,即若当前被选则改为不选,当前不选则改为被选。

     

    【输出格式】

    输出m行,第i行一个整数表示第i次事件后初音最少需要准备多少葱。

     

    【样例数据】

     

    cruise.in

    cruise.out

    5 8

    1 2

    1 3

    2 4

    2 5

    1 0 0 1 0

    5 4 2 1 2 5 3 2

    3

    2

    2

    1

    0

    0

    0

    2

     

    【数据范围】

    对于30%的数据,n,m≤3000。

    对于另30%的数据,开始时所有地点都不选,保证修改操作的地点当前是不选状态。

    对于100%的数据,1≤n,m≤200000,1≤x,y,Ai≤n。


    不同的只是边权值全部为1,且只需要统计走过的路径条数,而不是走过的距离,简单分析我们可以发现,其实就是上一道题的ans/2,因为原来是走过了还要走回去,相当于走两次。

    #include<bits/stdc++.h>
    #define N 200003
    #define LL long long
    using namespace std;
    int read()
    {
        int x=0,f=1;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    struct EDGE{
        int to,nextt;
    }w[N*2];
    int vis[N],tot=0,head[N],now[N],cnt=0;
    int f[N][23],dep[N],id[N],pos[N];
    int ans=0;
    set<int>s;
    void add(int a,int b)
    {
        tot++;
        w[tot].to=b;
        w[tot].nextt=head[a];
        head[a]=tot;
    }
    bool dfs(int x)
    {
        id[++cnt]=x;
        pos[x]=cnt;
        bool back=now[x];
        for(int i=head[x];i;i=w[i].nextt)
        {
            int v=w[i].to;
            if(v==f[x][0])continue;
            f[v][0]=x;
            for(int i=1;i<=20;++i)
              f[v][i]=f[f[v][i-1]][i-1];
            dep[v]=dep[x]+1;
            if(dfs(v))//说明fa为v这条路径是要放葱的,因为下面有要选择的点,而到那个点的路径唯一,必须经过fa为v这条路 
            {
                ans++;//放葱的路径数++ 
                back=1;
            }
        }
        return back;
    }
    int lca(int x,int y)
    {
        if(dep[x]<dep[y])swap(x,y);
        for(int i=20;i>=0;--i)if(dep[f[x][i]]>=dep[y])x=f[x][i];
        if(x==y)return x;
        for(int i=20;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    int dis(int x,int y)
    {
        int LCA=lca(x,y);
        return dep[x]+dep[y]-2*dep[LCA];
    }
    void modify(int x)
    {
        
        now[x]^=1;
        if(now[x]) s.insert(pos[x]);
        else s.erase(pos[x]);
        if(s.size()<=1){ans=0;return;}//存在只有一个和没有的情况,都要直接返回 
        set<int>::iterator pre,nxt;
        pre=s.lower_bound(pos[x]);//大于等于 
        nxt=pre;
        if(now[x])nxt++;
        if(pre==s.begin())pre=--s.end();
        else pre--;
        if(nxt==s.end()) nxt=s.begin();
        int dis1=dis(id[*pre],id[*nxt]);
        int dis2=dis(id[*pre],x);
        int dis3=dis(x,id[*nxt]);
        if(now[x])ans+=dis2+dis3-dis1;
        else ans-=dis2+dis3-dis1;
    }
    int main()
    {
        freopen("cruise.in","r",stdin);
        freopen("cruise.out","w",stdout);
        int n=read(),m=read();
        for(int i=1;i<n;++i)
        {
            int a=read(),b=read();
            add(a,b);add(b,a);
        }
        int go=0;
        for(int i=1;i<=n;++i)
        {
            now[i]=read();
            if(now[i])go=i;
        }
        if(!go)dep[1]=1,dfs(1);
        else dep[go]=1,dfs(go);//从一个被选择的点开始走一定更优 
        ans=ans*2;
        for(int i=1;i<=n;++i)
          if(now[i]) s.insert(pos[i]);
        for(int i=1;i<=m;++i)
        {
            int x=read();
            modify(x);
            printf("%d
    ",ans/2);
        }
    } 
    View Code

  • 相关阅读:
    设置圆角代码
    队列组的简单使用
    多线程的延时执行和一次性代码
    GCD线程间的通信
    GCD"牛逼的中枢调度器"
    线程间的通信
    KVO运行时
    iOS Programming Localization 本地化
    iOS Programming State Restoration 状态存储
    如何安装sql server2005 windows 8
  • 原文地址:https://www.cnblogs.com/yyys-/p/11260981.html
Copyright © 2011-2022 走看看