【DP】[NOI2013]快餐店

题目

https://www.luogu.com.cn/problem/P1399

思路

对于基环树,只需要断开换上的一条边然后求树的直径就可以了,可以证明断开一条边后对答案没有影响,因为距离肯定不会成一个环。
但是这复杂度不对
可以先对换上的每个外向树做一次求直径(最后的答案肯定大于等于它的一半的),求出f[i]表示以第i个点为起点,做大能在子树中延伸的长度。

然后对于换上i,j两点,他们的距离就是f[i]+f[j]+dis[i][j],dis[i][j]表示的是在环上i,j的直接距离。

拆换成链后就变成f[i]+f[j]+sum[j] - sum[i], sum[i]表示拆环成链后边权的前缀和,然后f[i]-sum[i]和f[j]-sum[j]直接开两个堆维护一下最大值就可以了,注意要考虑i == j的情况。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+77;
struct E
{
    int to,val;
};
vector<E> g[N];
int n,loop[N],sign,low[N],dfn[N],st[N],top,onloop[N],L,R;
ll dis[N],fl[N],pre[N],premx[N],
    ans = 1e15,mx = 0,lst[N],pans[N],sans[N],sumlst[N],oo = 0,sum = 0,summ = 0,
    o = 0,mxd = 0;
void Tarjan(int x,int fa) {
    dfn[x] = low[x] = ++sign,st[++top] = x;
    for(E i : g[x]) {
        int y = i.to;
        if(y == fa)
            continue;
        if(dfn[y])
            low[x] = min(low[x],dfn[y]);
        else
            Tarjan(y,x),low[x] = min(low[x],low[y]);
    }
    if(low[x] == dfn[x]) {
        int tmp;
        if(st[top] == x)
            top--;
        else
            do {
                tmp = st[top--],loop[++loop[0]] = tmp,onloop[tmp] = 1;
            } while (tmp != x);
    }
}
void dfs(int x,int fa,int &p,ll dis,int f) {
    if(dis >= mxd)
        mxd = dis,p = x;
    for(E i : g[x])
        if(i.to != fa && (!onloop[i.to] || i.to == f))
            dfs(i.to,x,p,dis + i.val,f);
}
int main() {
    cin >> n;
    for(int i = 1,x,y,z; i <= n; i++)
        cin >> x >> y >> z,g[x].push_back({ y,z }),g[y].push_back({ x,z });
    Tarjan(1,0);
    for(int i = 1; i <= loop[0]; i++) {
        mxd = 0,dfs(loop[i],0,L,0,loop[i]),dis[i] = mxd;
        dfs(L,0,R,0,loop[i]),oo = max(oo,mxd);
    }
    premx[1] = dis[loop[1]];
    for(E j : g[loop[1]])
        if(j.to == loop[loop[0]])
            lst[1] = j.val;
    for(int i = 2; i <= loop[0]; i++) {
        for(E j : g[loop[i]])
            if(j.to == loop[i - 1])
                lst[i] = j.val;
        sum += lst[i],premx[i] = max(sum + dis[i],premx[i - 1]);
    }
    mx = 0;
    for(int i = 1; i <= loop[0]; i++) {
        if(i != 1)
            summ += lst[i];
        pans[i] = max(pans[i - 1],dis[i] + summ + mx);
        mx = max(mx,dis[i] - summ);
    }
    mx = 0;
    for(int i = 1; i <= loop[0]; i++) sumlst[i] = sumlst[i - 1] + (i != 1 ? lst[i] : 0);
    for(int i = loop[0]; i >= 1; i--) {
        sans[i] = max(sans[i + 1],mx + dis[i] - sumlst[i]);
        mx = max(mx,dis[i] + sumlst[i]);
    }
    mx = 0;
    sans[loop[0] + 1] = pans[1] = 1e15;
    for(int i = loop[0]; i >= 1; i--) {
        ans = min(ans,max(mx + premx[i] + lst[1],max(pans[i],sans[i + 1])));
        mx = max(mx,sumlst[loop[0]] - sumlst[i] + dis[i]);
    }
    mx = -1e15;
    for(int i = 1; i <= loop[0]; i++) {
        o = max(o,dis[i] + sumlst[i] + mx);
        mx = max(mx,dis[i] - sumlst[i]);
    }
    printf("%.1lf\n",max(min(ans,o),oo) / 2.0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值