【树DP+基环树】[NOI2013]快餐店

题目描述

小 T 打算在城市 C 开设一家外送快餐店。送餐到某一个地点的时间与外卖店到该地点之间最短路径长度是成正比的,小 T 希望快餐店的地址选在离最远的顾客距离最近的地方。

快餐店的顾客分布在城市 C 的 N 个建筑中,这 N 个建筑通过恰好 N 条双向道路连接起来,不存在任何两条道路连接了相同的两个建筑。任意两个建筑之间至少存在一条由双向道路连接而成的路径。小 T 的快餐店可以开设在任一建筑中,也可以开设在任意一条道路的某个位置上(该位置与道路两端的建筑的距离不一定是整数)。

现给定城市 C 的地图(道路分布及其长度),请找出最佳的快餐店选址,输出其与最远的顾客之间的距离。

输入格式

第一行包含一个正整数 N,表示城市 C 中的建筑和道路数目。

接下来 N 行,每行 3 个整数,Ai,Bi,Li1iNL_i > 0),表明一条道路连接了建筑 AiBi,其长度为 Li

输出格式

包含一个实数,四舍五入保留恰好一位小数,表示最佳快餐店选址距离最远用户的距离。

注意:你的结果必须恰好有一位小数,小数位数不正确不得分。

样例一

input

4
1 2 1
1 4 2
1 3 2
2 4 1

output

2.0

explanation

最优选址为建筑 1。到达 4 个建筑的距离分别为 0,1,2,2

样例二

input

5
1 5 100
2 1 77
3 2 80
4 1 64
5 3 41

output

109.0

explanation

最佳选址为 15 这条边上,距离 1 的距离为 32 的位置。

样例三

见样例数据下载。

限制与约定

对于 10% 的数据,N80,Li=1

对于 30% 的数据,N600,Li100

对于 60% 的数据,N2000,Li109

对于 100% 的数据,N105,Li109

时间限制:2s

空间限制:512MB

分析

首先对于一棵树,显然答案等于树的直径除以2,然后我们来考虑基环树。
我们可以把基环树看做一个环+一个森林,其中每棵树的根就是这棵树和环的公共点。
显然环上总有一条边一定不会被走到,我们枚举环上的边,删掉它,然后求直径,时间复杂度O(n2),妥妥的60分。
我们能不能快速求直径呢?
我们考虑直径有哪些情况:

  1. 在树的内部
    这个我们直接预处理,然后取一个最大值,记作mx,因为删除环上的边对于这种情况没有影响。
  2. 经过了环上的边
    这种情况是我们重点需要解决的问题。

接下来,我们来讨论这个问题:
注意,为方便表述,以下的编号为破环为链后数组的下标
首先,破环为链(即将这个环的序列再复制一遍),成为一个序列,每次删边之后就对应序列的一个区间,我们令Di表示以i为根的子树中深度最深的点的深度(即子树中以i为一个端点的最长链),disti,j标表示i,j两个点之间的距离,那么经过环的最长链就为

Di+disti,j+Dj  i,j[l,r]
l,r为当前对应的区间。
其实disti,j可以用前缀和来解决,在序列上求一个边权的前缀和,在disti,j=sumjsumi  i<j
那么最长链就为Di+sumjsumi+Dj=Disumi+Dj+sumj,我们用线段树分别维护一下DisumiDi+sumi的最大值即可。不过这两个东西的最大值可能在同一个位置出现,所以再维护一下次大值。每次对线段树询问对应区间即可。
这里有一个问题,假设DisumiDi+sumi的最大值分别出现在i,j,怎样保证i<j呢?
我们分类讨论一下
有三个点i,j,ki<j<k

  • 如果两个最大值不在同一处出现,假设Di+sumi的最大值出现在j这个位置,而且Disumi的最大值出现在k这个位置,那么
    Dksumk>DjsumjDk>Djsumj+sumk

    因为
    sumk>sumj
    所以
    Dk>DjDk+sumk>Dj+sumj
    与假设相悖
  • 如果两个最大值在同一处出现,假设出现在j,那么我们假设Disumi的最大值出现在k,显然根据上一个证明,Dk+sumk>Di+sumi,而Dj+sumj+Dksumk<Djsumj+Dk+sumk,所以这种情况不会被算进最优解。

所以,我们可以放心地使用这种方法。时间复杂度O(nlog2n)

代码

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 100000
#define INF 0x7fffffffffffffffll
typedef long long LL;
int n,bgc,cir[MAXN*2+10],cnt;
bool vis[MAXN+10];
LL ans=INF,f[MAXN+10][3],mx,sum[MAXN*2+10];
void Read(int &x){
    char c;
    while(c=getchar(),c!=EOF)
        if(c>='0'&&c<='9'){
            x=c-'0';
            while(c=getchar(),c>='0'&&c<='9')
                x=x*10+c-'0';
            ungetc(c,stdin);
            return;
        }
}
struct node{
    int v,wt;
    bool vis;
    node *next,*back;
}*adj[MAXN+10],edge[MAXN*2+10],*ecnt=edge,*pre[MAXN+10];
inline void addedge(int u,int v,int wt){
    node *p=++ecnt;
    p->v=v;
    p->wt=wt;
    p->vis=0;
    p->next=adj[u];
    adj[u]=p;
    p=p->back=++ecnt;
    p->v=u;
    p->wt=wt;
    p->vis=0;
    p->next=adj[v];
    adj[v]=p;
    p->back=ecnt-1;
}
void read(){
    Read(n);
    int i,u,v,wt;
    for(i=1;i<=n;i++){
        Read(u),Read(v),Read(wt);
        addedge(u,v,wt);
    }
}
void find_circle(int u){
    if(vis[u]){
        bgc=u;
        return;
    }
    vis[u]=1;
    for(node *p=adj[u];p;p=p->next){
        if(!p->vis){
            pre[p->v]=p;
            p->back->vis=p->vis=1;
            find_circle(p->v);
        }
    }
}
void dfs(int u,int fa){
    for(node *p=adj[u];p;p=p->next){
        if(p->v!=fa&&!p->vis){
            dfs(p->v,u);
            if(f[p->v][0]+p->wt>f[u][0]){
                f[u][1]=f[u][0];
                f[u][0]=f[p->v][0]+p->wt;
            }
            else if(f[p->v][0]+p->wt>f[u][1])
                f[u][1]=f[p->v][0]+p->wt;
            f[u][2]=max(f[u][2],f[p->v][2]);
        }
    }
    f[u][2]=max(f[u][2],f[u][0]+f[u][1]);
}
namespace SegmentTree{
struct node{
    LL mx,smx;
    int mxpos;
    inline node(){
    }
    inline node(LL mx,LL smx,int mxpos):mx(mx),smx(smx),mxpos(mxpos){
    }
    node *ch[2];
}tree[MAXN*8+10],*root[2],*tcnt=tree;
void update(node *p){
    if(p->ch[0]->mx>p->ch[1]->mx){
        p->mx=p->ch[0]->mx,p->mxpos=p->ch[0]->mxpos;
        p->smx=max(p->ch[0]->smx,p->ch[1]->mx);
    }
    else{
        p->mx=p->ch[1]->mx,p->mxpos=p->ch[1]->mxpos;
        p->smx=max(p->ch[1]->smx,p->ch[0]->mx);
    }
}
node merge_ans(const node &a,const node &b){
    if(a.mx>b.mx)
        return node(a.mx,max(a.smx,b.mx),a.mxpos);
    return node(b.mx,max(a.mx,b.smx),b.mxpos);
}
void build(node *&p,int l,int r,int ff){
    p=++tcnt;
    if(l==r){
        p->mx=f[cir[l]][0]+ff*sum[l];
        p->mxpos=l;
        p->smx=-INF;
        return;
    }
    int mid((l+r)>>1);
    build(p->ch[0],l,mid,ff);
    build(p->ch[1],mid+1,r,ff);
    update(p);
}
node get_ans(node *p,int l,int r,int ll,int rr){
    if(ll<=l&&r<=rr)
        return *p;
    if(ll>r||rr<l)
        return node(-INF,-INF,0);
    int mid((l+r)>>1);
    return merge_ans(get_ans(p->ch[0],l,mid,ll,rr),get_ans(p->ch[1],mid+1,r,ll,rr));
}
}
void solve(){
    find_circle(1);
    int i=bgc;
    for(node *p=edge+1;p<=ecnt;p++)
        p->vis=0;
    do{
        cir[++cnt]=i;
        pre[i]->vis=pre[i]->back->vis=1;
        i=pre[i]->back->v;
    }while(i!=bgc);
    for(i=1;i<=cnt;i++){
        dfs(cir[i],0),mx=max(mx,f[cir[i]][2]);
        cir[i+cnt]=cir[i];
    }
    for(i=2;i<=2*cnt;i++)
        sum[i]=sum[i-1]+pre[cir[i-1]]->wt;
    SegmentTree::build(SegmentTree::root[0],1,cnt*2,1);
    SegmentTree::build(SegmentTree::root[1],1,cnt*2,-1);
    SegmentTree::node a,b;
    for(i=1;i<=cnt;i++){
        a=SegmentTree::get_ans(SegmentTree::root[0],1,cnt*2,i,i+cnt-1);
        b=SegmentTree::get_ans(SegmentTree::root[1],1,cnt*2,i,i+cnt-1);
        if(a.mxpos==b.mxpos)
            ans=min(ans,max(a.mx+b.smx,a.smx+b.mx));
        else
            ans=min(ans,a.mx+b.mx);
    }
    ans=max(ans,mx);
}
int main()
{
    read();
    solve();
    printf("%.1lf\n",ans/2.0);
}
posted @ 2016-07-07 23:39  outer_form  阅读(811)  评论(0编辑  收藏  举报