zoukankan      html  css  js  c++  java
  • 虚树------sdoi2011<消耗战>

    卡着时间过得,大概是因为全用了ll,时间涨了一倍吧??

    懒得改了,第一道虚树还是思路比较重要

    下面这段文字是复制来的:

    给出一棵树.

    每次询问选择一些点,求一些东西.这些东西的特点是,许多未选择的点可以通过某种方式剔除而不影响最终结果.

    于是就有了建虚树这个技巧.....

    我们可以用log级别的时间求出点对间的lca....

    那么,对于每个询问我们根据原树的信息重新建树,这棵树中要尽量少地包含未选择节点. 这棵树就叫做虚树.

    接下来所说的"树"均指虚树,原来那棵树叫做"原树".

    构建过程如下:

    按照原树的dfs序号(记为dfn)递增顺序遍历选择的节点. 每次遍历节点都把这个节点插到树上.

    首先虚树一定要有一个根. 随便扯一个不会成为询问点的点作根.

    维护一个栈,它表示在我们已经(用之前的那些点)构建完毕的虚树上,以最后一个插入的点为端点的DFS链.

    设最后插入的点为p(就是栈顶的点),当前遍历到的点为x.我们想把x插入到我们已经构建的树上去.

    求出lca(p,x),记为lca.有两种情况:

      1.p和x分立在lca的两棵子树下.

      2.lca是p.

      (为什么lca不能是x?

       因为如果lca是x,说明dfn(lca)=dfn(x)<dfn(a),而我们是按照dfs序号遍历的,于是dfn(a)<dfn(x),矛盾.)

     对于第二种情况,直接在栈中插入节点x即可,不要连接任何边(后面会说为什么).

    对于第一种情况,要仔细分析.

    我们是按照dfs序号遍历的(因为很重要所以多说几遍......),有dfn(x)>dfn(p)>dfn(lca).

    这说明什么呢? 说明一件很重要的事:我们已经把lca所引领的子树中,p所在的子树全部遍历完了!

      简略的证明:如果没有遍历完,那么肯定有一个未加入的点h,满足dfn(h)<dfn(x),

            我们按照dfs序号递增顺序遍历的话,应该把h加进来了才能考虑x.

    这样,我们就直接构建lca引领的,p所在的那个子树. 我们在退栈的时候构建子树.

    p所在的子树如果还有其它部分,它一定在之前就构建好了(所有退栈的点都已经被正确地连入树中了),就剩那条链.

    如何正确地把p到lca那部分连进去呢?

    设栈顶的节点为p,栈顶第二个节点为q.

    重复以下操作:

      如果dfn(q)>dfn(lca),可以直接连边q->p,然后退一次栈.

      如果dfn(q)=dfn(lca),说明q=lca,直接连边lca->p,此时子树已经构建完毕.

      如果dfn(q)<dfn(lca),说明lca被p与q夹在中间,此时连边lca->q,退一次栈,再把lca压入栈.此时子树构建完毕.

        如果不理解这样操作的缘由可以画画图.....

    最后,为了维护dfs链,要把x压入栈. 整个过程就是这样.....

    题解:

    对于这一道题目,朴素的树形dp是nm的

    而容易发现,某些路径中的点是没有用的,所以考虑建立一颗虚树

    两条边之间的权值就是实际权值的最小值

    当然这题会发现有个性质就是如果某个点在另一个点的子树中,这个点是不用考虑的(事实上这个优化对效率并没有什么卵用)

    至于那个dp,没什么难度,就看代码吧

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    #define maxn 1000000
    #define maxn2 300000
    #define INF 1e15
    #define ll long long
    struct re{
        ll a,b,c;
    }a[maxn],a2[maxn];
    ll gg,m,n,l,l1,number[maxn],dep[maxn],head[maxn],
    head2[maxn],ql[maxn],bz1[maxn2][22],bz2[maxn2][22],
    nowtime[maxn],now,f[maxn],b[maxn],k,st[maxn],top;
    void dfs(ll x,ll fa)
    {
        number[x]=++now;
        bz1[x][0]=fa;
        ll u=head[x];
        while (u)
        {
            ll v=a[u].b;
            if (v!=fa)
            { 
              bz2[v][0]=a[u].c; dep[v]=dep[x]+1;
              dfs(v,x);
            }
            u=a[u].a;
        }
    }
    bool cmp(ll x,ll y)
    {
        return(number[x]<number[y]);
    }
    ll lca(ll x,ll y)
    {
        if (dep[x]<dep[y]) swap(x,y);
        for (ll i=20;i>=0;i--)
          if (dep[x]-(1<<i)>=dep[y]) x=bz1[x][i];
        if (x==y) return(x);
        for (ll i=20;i>=0;i--)
          if (bz1[x][i]!=bz1[y][i])
            x=bz1[x][i],y=bz1[y][i];
        return(bz1[x][0]);
    }
    ll query(ll x,ll y)
    {
        if (dep[x]<dep[y]) swap(x,y);
        ll ans=INF;
        for (ll i=20;i>=0;i--)
          if (dep[x]-(1<<i)>=dep[y]) 
            ans=min(ans,bz2[x][i]),x=bz1[x][i];
        if (x==y) return(ans);
        for (ll i=20;i>=0;i--)
          if (bz1[x][i]!=bz1[y][i])
          {
              ans=min(ans,bz2[x][i]);
            ans=min(ans,bz2[y][i]);
            x=bz1[x][i],y=bz1[y][i];
          }
        ans=min(ans,bz2[x][0]);
        ans=min(ans,bz2[y][0]);
        return(ans);
    }
    void dp(ll x,ll fa)
    {
        ll u=head2[x],tmp=0;
        while (u)
        {
            ll v=a2[u].b;
            if (v!=fa)
            {
              f[v]=a2[u].c;
              dp(v,x);
              tmp+=f[v];
            }
            u=a2[u].a;
        }
        if (nowtime[x]!=gg) f[x]=min(f[x],tmp);
    }
    void arr(ll x,ll y,ll z)
    {
        a[++l1].a=head[x];
        a[l1].b=y;
        a[l1].c=z;
        head[x]=l1;
    }
    void arr2(ll x,ll y)
    {
      if (x==y) return;
        a2[++l].a=head2[x];
        ql[l]=x;
        a2[l].b=y;
        a2[l].c=query(x,y);
        head2[x]=l;
    }
    deque <ll> q;
    void solve()
    {
        for (ll i=1;i<=l;i++) head2[ql[i]]=0;l=0;//清理head的一种不太浪费的方法
        q.clear();
        q.push_back(b[1]); 
        for (ll i=2;i<=k;i++)
          //if (lca(b[i],b[q.back()])!=q.back()) 
            q.push_back(b[i]);  //按照上面说的如果在别人子树中就被丢弃
        top=0;
        st[++top]=1;
        while (!q.empty())
        {
            ll now=q.front(),tmp=lca(st[top],now);
            q.pop_front();
            while (true)
            {
                if (dep[tmp]>=dep[st[top-1]])
                {
                    arr2(tmp,st[top]); 
                    arr2(st[top],tmp); top--;
                    if (st[top]!=tmp) st[++top]=tmp;
                    break;
                }
                arr2(st[top-1],st[top]); 
                arr2(st[top],st[top-1]); top--;
            }
            //if (st[top]!=now)  //这句是为了防止有相同点 这题里可以不需要
        st[++top]=now;
        }
        while (top>1) 
        {
          arr2(st[top],st[top-1]);
          arr2(st[top-1],st[top]),top--;
        }
        f[1]=INF;dp(1,0);
        cout<<f[1]<<endl;
    }
    int main()
    {
        std::ios::sync_with_stdio(false);
        cin>>n;
        ll c,d,e;
        for (ll i=1;i<=n-1;i++)
        {
            cin>>c>>d>>e;
            arr(c,d,e); arr(d,c,e);
        }
        for (ll i=0;i<=20;i++)
          for (ll j=0;j<=maxn2-10;j++) bz2[j][i]=INF;
        dfs(1,0);
        for (ll i=1;i<=20;i++)
          for (ll j=1;j<=n;j++)
            {
                bz1[j][i]=bz1[bz1[j][i-1]][i-1];
                bz2[j][i]=min(bz2[j][i-1],bz2[bz1[j][i-1]][i-1]);
            }
        cin>>m;
        for (gg=1;gg<=m;gg++)
        {
            cin>>k;
            for (ll i=1;i<=k;i++) 
            {
                cin>>b[i];
                nowtime[b[i]]=gg;
            }
            sort(b+1,b+1+k,cmp); 
            solve();
        }
    } 
        top=0;
        st[++top]=1;
        while (!q.empty())
        {
            ll now=q.front(),tmp=lca(st[top],now);
            q.pop_front();
            while (true)
            {
                if (dep[tmp]>=dep[st[top-1]])
                {
                    arr2(tmp,st[top]); 
                    arr2(st[top],tmp); top--;
                    if (st[top]!=tmp) st[++top]=tmp;
                    break;
                }
                arr2(st[top-1],st[top]); 
                arr2(st[top],st[top-1]); top--;
            }
            if (st[top]!=now) st[++top]=now;
        }

         while (top>1)
          {
             arr2(st[top],st[top-1]);
             arr2(st[top-1],st[top]),top--;
          }

     

    真正和虚树有关的就上面这么一点,基本背板子就可以了

    自己归纳的虚树步骤

    首先找一个不可能出现的点作为第一个节点

    然后对于新加入的点,求出其与st[top]的lca

    当lca>=st[top-1]的时候 就连边st[top-1]------->lca 结束此次循环

    当lca<st[top-1]的时候 九离岸边st[top-1]------>st[top] 然后递归一下

    做完后判断新加入的点是否存在,如果不存在就加入到栈中

    最后再将剩余的连完

    这个步骤的原理上面已经说了 感觉理解也是挺直观的

  • 相关阅读:
    VLC在web系统中应用(xvlcplugin 即如何把VLC嵌入HTML中)
    mysql in 排序
    EditPlus v3.31 注册码
    UTF8编码判断
    zend framework 获取邮箱内容 编码转换 quoted_printable_decode | base64_decode
    String path = request.getContextPath(....拼装当前网页的相对路径
    【转】input中id和name的区别
    JSON基础知识
    【转】 jdbc.properties
    JSP页面传值乱码过滤
  • 原文地址:https://www.cnblogs.com/yinwuxiao/p/8447567.html
Copyright © 2011-2022 走看看