【NOI2013】快餐店的解题——线段树+dfs遍历

题目:luogu1399.

题目大意:在一棵基环树上,找到一个节点或边上的一个点,使其到达距离它最远的节点最近,并输出这个距离.

一般基环树的题都需要先考虑是树的情况再拓展到基环树上,所以我们可以先考虑再树上怎么处理.

很显然的,这个距离若是在树上,就是这棵树的直径的一半.

那么这道题好像就是可以直接用O(n)处理出树的直径,然后取一半就可以了.

可是这不是树,是基环树,所以我们需要暴力枚举删除那一条边,然后O(n)取得树的直径,时间复杂度O(n^2).

可是这样就超时了,所以我们要考虑优化.

我们考虑先找出那个环,把这个环上的所有节点搞下来,复制一份破环成链,在每个点下面都以这个节点为根建一颗树.

之后变成链了之后,我们就可以对环上每个节点为根的子树中的直径先比出大小搞出最大的直径.

暴力算出非环点到它所在子树的根的距离,并求出每个环点i子树中离它最远点的距离d[i],时间复杂度O(n).

可以发现,每个节点i的d[i]是不变的,我们用两个节点i,j来表示两个环点j>i,并用前缀和S[i]表示第1到i个环点的边权总和,那么我们就是要找max(d[i]+d[j]+S[j]-S[i]).

那么我们就可以用两棵线段树来维护d[i]+S[i]+d[i]-S[i]的最大值,做到O(log(n))回答区间[L,R]内的询问.

至于这颗线段树可以维护d[i]-S[i]的最大值,d[j]+S[j]的最大值,以及d[i]+d[j]-S[i]+S[j]来实现.

之后我们枚举左端点l,每一次取区间[l,l+n-1]的最大值,之后找到最小值.

注意一定要保证i不等于j.

为什么要枚举左端点而不是直接求解呢?

你怎么不去问问神奇海螺呢?

那是因为这是个环,我们需要枚举删除哪条边.

这样就可以O(nlog(n))的跑过这道题啦.

AC代码如下:

#include<bits/stdc++.h>
  using namespace std;
typedef long long LL;
const int N=100000;
const LL INF=10000000000000000;
int n;
struct side{
  int y,next;
  LL v;
}e[N*2+1];
int top,lin[N+1],ring[N*2+1],tt;
LL S[N*2+1],d[N+1],ans,minn=INF,maxx=-INF;
struct tree{
  int l,r;
  LL max1,max2,maxx;
}tr[N*10];
bool use[N+1];
void ins(int X,int Y,LL V){
  e[++top].y=Y;e[top].v=V;
  e[top].next=lin[X];
  lin[X]=top;
}
bool find(int k,int last){
  use[k]=1;
  for (int i=lin[k];i;i=e[i].next){
    if (e[i].y==last) continue;
    if (use[e[i].y]){
      ring[++tt]=k;
      S[tt]=e[i].v;
      use[e[i].y]=0;
      return true;
    }else if (find(e[i].y,k)){
      ring[++tt]=k;
      S[tt]=S[tt-1]+e[i].v;
      if (use[k]) return true;
      else return false;
    }
  }
  return false;
}
void dfs(int k){
  use[k]=1;
  for (int i=lin[k];i;i=e[i].next){
    if (use[e[i].y]) continue;
    dfs(e[i].y);
    maxx=max(maxx,d[k]+d[e[i].y]+e[i].v);
    d[k]=max(d[k],d[e[i].y]+e[i].v);
  }
}
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  tr[k].max1=tr[k].max2=tr[k].maxx=-INF;
  if (L==R) return;
  int mid=L+R>>1;
  build(L,mid,k<<1);
  build(mid+1,R,k<<1|1);
}
void add(int x,LL num1,LL num2,int k=1){
  if (tr[k].l==tr[k].r){
    tr[k].max1=num1;tr[k].max2=num2;
    tr[k].maxx=0LL;      //保证i不等于j,坑了我半年 
    return;
  }
  int mid=tr[k].l+tr[k].r>>1;
  if (x<=mid) add(x,num1,num2,k<<1);
  else add(x,num1,num2,k<<1|1);
  tr[k].max1=max(tr[k<<1].max1,tr[k<<1|1].max1);
  tr[k].max2=max(tr[k<<1].max2,tr[k<<1|1].max2);
  tr[k].maxx=max(max(tr[k<<1].maxx,tr[k<<1|1].maxx),tr[k<<1].max1+tr[k<<1|1].max2);
}
struct tree1{
  LL maxx,max1,max2;
};
tree1 query(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r) {
    tree1 o;
    o.maxx=tr[k].maxx;
    o.max1=tr[k].max1;
    o.max2=tr[k].max2;
    return o;
  }
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return query(L,R,k<<1);
  else if (L>mid) return query(L,R,k<<1|1);
    else {
      tree1 u=query(L,mid,k<<1),v=query(mid+1,R,k<<1|1),o;
      o.max1=max(u.max1,v.max1);
      o.max2=max(u.max2,v.max2);
      o.maxx=max(max(u.maxx,v.maxx),u.max1+v.max2);
      return o;
    }
}
inline void into(){
  scanf("%d",&n);
  int x,y;
  LL v;
  for (int i=1;i<=n;i++){
    scanf("%d%d%lld",&x,&y,&v);
    ins(x,y,v);ins(y,x,v);
  }
}
inline void work(){
  find(1,0);
  for (int i=0;i<=n;i++) use[i]=0;
  for (int i=1;i<=tt;i++) use[ring[i]]=1;
  for (int i=1;i<=tt;i++) {
    dfs(ring[i]);
    ring[i+tt]=ring[i];
      S[i+tt]=S[i]+S[tt];
  }
  build(1,tt*2);
  for (int i=1;i<=2*tt;i++)
    add(i,d[ring[i]]-S[i],d[ring[i]]+S[i]);
  for (int i=1;i<=tt;i++)
    minn=min(minn,query(i,i+tt-1).maxx);
  ans=max(maxx,minn);
}
inline void outo(){
  printf("%.1lf\n",ans/2.0);
}
int main(){
  into();
  work();
  outo();
  return 0;
}

这道题调了好久,看起来线段树掌握的还不够熟练.

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/80633513