• 【NOI2013T6】快餐店-环套树+树形DP+线段树


    测试地址:快餐店
    做法:这题超级神,就是比较蛋疼……orz
    这题需要用到环套树+树形DP+线段树。
    首先如果题目中的图是树的话,答案显然是树的直径/2,而题目中描述的图显然是一棵环套树,那么环套树的情况怎么办呢?容易证明,答案是去掉某一条环边后剩下的树的直径/2。那么我们枚举去掉的环边,问题转化为求所剩树的直径。
    显然暴力做是O(N2)的,可以通过60分的数据,但是还不够。注意到直径可能是以下两种情况之一:1.环上的一些边+端点引出的两棵外向树中经过根的最长路径;2.某一棵外向树的直径。我们可以先算出第二种情况的值,然后问题就集中在如何求第一种情况。先树形DP出外向树中过根的最长路径,考虑破环为链(即把环上的点序列复制一遍),设环上的点数为k,那么新的序列中点数就是2k,将这些点编号为12k,设mx(i)为点i引出的外向树中过根的最长路径,sum(i)为点i与点1的距离,这里的距离不是原来环上的距离,而是在环上按照序列顺序走,所经过的距离。我们要枚举删掉哪一条边,实际上就是在枚举边被砍断后的环区间,这些区间在上面的序列中就是[1,k],[2,k+1],...,[k,2k1],对于每个区间[l,r],我们要求的直径长度就是max(sum(j)sum(i)+mx(i)+mx(j)),即max(mx(j)+sum(j))+max(mx(i)sum(i)),其中li<jr,那么我们就可以用线段树来分别求两个部分的最大值,可以证明求出的ij,但是因为要求i<j,所以如果求出来的i=j,那么就用两个部分的次大值分别替换原来的最大值,然后在求出的两个值里取最大的即可。注意求出来后要和每棵外向树中的直径做比较,然后才能求出真正的直径。在所有的直径里面,取最小值/2就是最后的答案了,时间复杂度为O(NlogN)
    好像还有单调队列O(N)的做法,研究了半天发现不会,所以就先用线段树水一水了……
    以下是本人代码(不知道哪里写疵了只得90分,而且又臭又长,强烈不推荐阅读):

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #define ll long long
    using namespace std;
    int n,tot=0,first[100010],nex[100010],pre[100010],lp[100010],st,lpp;
    struct edge {int v,next;ll d;} e[200010];
    ll lpd[100010],mx[100010],smx[100010],inf,ans,mxlen=0;
    ll mxseg[800010][2],smxseg[800010][2],mxpseg[800010][2],sum[200010]={0};
    ll mtmp[2],stmp[2],tmpp[2];
    bool vis[100010]={0},inlp[100010]={0},flag;
    
    void insert(int a,int b,ll d)
    {
      e[++tot].v=b,e[tot].d=d,e[tot].next=first[a],first[a]=tot;
    }
    
    void buildloop(int last,int now,ll d)
    {
      nex[last]=now;
      pre[now]=last;
      lpd[last]=d;
      lpp++;
      inlp[now]=1;
    }
    
    bool find_loop(int v,int f)
    {
      vis[v]=1;
      for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=f)
        {
          if (vis[e[i].v])
          {
            buildloop(e[i].v,v,e[i].d);
            st=e[i].v;flag=1;
            return 1;
          }
          else if (find_loop(e[i].v,v))
          {
            if (flag) buildloop(e[i].v,v,e[i].d);
            if (v==st) flag=0;
            return 1;
          }
        }
      return 0;
    }
    
    void treedp(int v,int f)
    {
      mx[v]=smx[v]=0;
      for(int i=first[v];i;i=e[i].next)
        if (!inlp[e[i].v]&&e[i].v!=f)
        {
          int x=e[i].v;
          treedp(x,v);
          if (mx[x]+e[i].d>mx[v])
          {
            smx[v]=mx[v];
            mx[v]=mx[x]+e[i].d;
          }
          else if (mx[x]+e[i].d>smx[v])
          {
            smx[v]=mx[x]+e[i].d;
          }
        }
      mxlen=max(mxlen,mx[v]+smx[v]);
    }
    
    void pushup(int no)
    {
      for(int i=0;i<=1;i++)
      {
        if (mxseg[no<<1][i]>mxseg[no<<1|1][i])
        {
          mxpseg[no][i]=mxpseg[no<<1][i];
          mxseg[no][i]=mxseg[no<<1][i];
        }
        else
        {
          mxpseg[no][i]=mxpseg[no<<1|1][i];
          mxseg[no][i]=mxseg[no<<1|1][i];
        }
        if (smxseg[no<<1][i]>mxseg[no<<1|1][i]) smxseg[no][i]=smxseg[no<<1][i];
        else if (smxseg[no<<1|1][i]>mxseg[no<<1][i]) smxseg[no][i]=smxseg[no<<1|1][i];
             else smxseg[no][i]=min(mxseg[no<<1][i],mxseg[no<<1|1][i]);
      }
    }
    
    void buildtree(int no,int l,int r)
    {
      if (l==r)
      {
        mxseg[no][0]=mx[lp[l]]+sum[l];
        mxseg[no][1]=mx[lp[l]]-sum[l];
        smxseg[no][0]=smxseg[no][1]=-inf;
        mxpseg[no][0]=mxpseg[no][1]=l;
        return;
      }
      int mid=(l+r)>>1;
      buildtree(no<<1,l,mid);
      buildtree(no<<1|1,mid+1,r);
      pushup(no);
    }
    
    void findmax(int no,int l,int r,int s,int t,bool i)
    {
      if (l>=s&&r<=t)
      {
        if (mtmp[i]==-inf)
        {
          tmpp[i]=mxpseg[no][i];
          stmp[i]=smxseg[no][i];
          mtmp[i]=mxseg[no][i];
        }
        else if (mxseg[no][i]>mtmp[i])
        {
          tmpp[i]=mxpseg[no][i];
          stmp[i]=mtmp[i];
          mtmp[i]=mxseg[no][i];
        }
        else if (mxseg[no][i]>stmp[i]) stmp[i]=mxseg[no][i];
        return;
      }
      int mid=(l+r)>>1;
      if (s<=mid) findmax(no<<1,l,mid,s,t,i);
      if (t>mid) findmax(no<<1|1,mid+1,r,s,t,i);
    }
    
    void work()
    {
      int x=nex[st],posnow=2;
      lp[1]=st;
      while(x!=st)
      {
        lp[posnow]=x;
        sum[posnow]=sum[posnow-1]+lpd[pre[x]];
        x=nex[x];posnow++;
      }
      do
      {
        lp[posnow]=x;
        sum[posnow]=sum[posnow-1]+lpd[pre[x]];
        x=nex[x];posnow++;
      }while(x!=st);
    
      buildtree(1,1,lpp<<1);
      for(int i=1;i<=lpp;i++)
      {
        int j=i+lpp-1;
        mtmp[0]=mtmp[1]=stmp[0]=stmp[1]=-inf;
        findmax(1,1,lpp<<1,i,j,0);
        findmax(1,1,lpp<<1,i,j,1);
        if (tmpp[0]==tmpp[1])
          ans=min(ans,max(mxlen,max(stmp[0]+mtmp[1],stmp[1]+mtmp[0])));
        else ans=min(ans,max(mxlen,mtmp[0]+mtmp[1]));
      }
    }
    
    int main()
    {
      scanf("%d",&n);
      for(int i=1;i<=n;i++)
      {
        int a,b;ll d;
        scanf("%d%d%lld",&a,&b,&d);
        insert(a,b,d),insert(b,a,d);
      }
    
      find_loop(1,0);
      int x=st;
      do
      {
        treedp(x,0);
        x=nex[x];
      }while(x!=st);
    
      inf=100000000;
      inf*=inf;
      ans=inf;
      work();
    
      printf("%.1lf",(double)ans/2);
    
      return 0;
    }
    
  • 相关阅读:
    从U盘安装Windows 7 / Vista / 2008
    Windows 7 Enterprise 微软官方90天评估序列号
    Windows 2008 Vista 安装sp2后释放C盘空间
    Windows 7 一年试用批处理
    Windows 2008 R2 试用版序列号
    又出来一个OEM的序列号
    查看 windows 7 激活信息的相关命令
    最新一组OEM Key
    Windows 7 / Vista 分区问题
    MSN 9 多开设置
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793660.html
Copyright © 2020-2023  润新知