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

测试地址:快餐店
做法:这题超级神,就是比较蛋疼……orz
这题需要用到环套树+树形DP+线段树。
首先如果题目中的图是树的话,答案显然是树的直径 /2 ,而题目中描述的图显然是一棵环套树,那么环套树的情况怎么办呢?容易证明,答案是去掉某一条环边后剩下的树的直径 /2 。那么我们枚举去掉的环边,问题转化为求所剩树的直径。
显然暴力做是 O(N2) 的,可以通过60分的数据,但是还不够。注意到直径可能是以下两种情况之一:1.环上的一些边+端点引出的两棵外向树中经过根的最长路径;2.某一棵外向树的直径。我们可以先算出第二种情况的值,然后问题就集中在如何求第一种情况。先树形DP出外向树中过根的最长路径,考虑破环为链(即把环上的点序列复制一遍),设环上的点数为 k ,那么新的序列中点数就是2k,将这些点编号为 1 2k,设 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值