[luogu 1399][NOI2013]快餐店

版权声明:蒟蒻写文章不容易啊,各位大佬转载要告诉我一声,QAQ https://blog.csdn.net/qq_38944163/article/details/89470928

link:https://www.luogu.org/problemnew/show/P1399


题意

给出一个基环树,每条边有一个边权,在图上求出一个点(可以在边上)使得它到图上每个节点的最大距离最小,输出那个距离.


题解

可以直接看代码
一个和很直观的思路,对于基环树,只需要断开换上的一条边然后求树的直径就可以了,可以证明断开一条边后对答案没有影响,因为距离肯定不会成一个环。
但是这样也是 o ( n 2 ) o(n^2)
可以先对换上的每个外向树做一次求直径(最后的答案肯定大于等于它的一半的),求出 f [ i ] f[i] 表示以第 i i 个点为起点,做大能在子树中延伸的长度。
然后对于换上 i , j i,j 两点,他们的距离就是 f [ i ] + f [ j ] + d i s [ i ] [ j ] f[i]+f[j]+dis[i][j] , d i s [ i ] [ j ] dis[i][j] 表示的是在环上 i , j i,j 的直接距离。
拆换成链后就变成 f [ i ] + f [ j ] + s u m [ j ] s u m [ i ] f[i]+f[j]+sum[j] - sum[i] , s u m [ i ] sum[i] 表示拆环成链后边权的前缀和,然后 f [ i ] s u m [ i ] f[i]-sum[i] f [ j ] s u m [ j ] f[j]-sum[j] 直接开两个堆维护一下最大值就可以了,注意要考虑 i = = j i == j 的情况。
是不是很简单(毒瘤)

#include<bits/stdc++.h>
#define int long long
#define N 1000005
using namespace std;
struct edge{
    int v, nxt, c;
}e[N << 1];
int p[N], eid;
void init(){
    memset(p, -1, sizeof p);
    eid = 0;
}
void insert(int u, int v, int c){
    e[eid].v = v;
    e[eid].c = c;
    e[eid].nxt = p[u];
    p[u] = eid ++;
}
int dfn[N], n, fa[N], f[N], F, ring[N], cnt, ha[N], ans, tot, sum[N], val[N];
void findring(int u){
    if(dfn[u]) F = dfn[u];
    if(F) return;
    dfn[u] = ++ tot;
    for(int i = p[u]; i + 1; i = e[i].nxt){
        int v = e[i].v;
        if(v == fa[u]) continue;
        fa[v] = u;
        findring(v);
    }
    if(F) ring[++ cnt] = u, ha[u] = 1;
    if(F == dfn[u]) F = 0;
}
void dfs(int u){ //dp 
    for(int i = p[u]; i + 1; i = e[i].nxt){
        int v = e[i].v;
        if(v == fa[u] || ha[v]) continue;
        fa[v] = u;
        dfs(v);
        ans = max(ans, f[u] + f[v] + e[i].c);//求树的直径 
        f[u] = max(f[u], f[v] + e[i].c);//第u为起点,做大能在子树中延伸的长度 
    }
}
struct cmp1{int operator()(int x, int y){
    return f[ring[x]] - sum[x] < f[ring[y]] - sum[y];
}};
struct cmp2{int operator()(int x, int y){
    return f[ring[x]] + sum[x] < f[ring[y]] + sum[y];
}};
priority_queue<int, vector<int>, cmp1> q1;//维护f[i]-sum[i] 
priority_queue<int, vector<int>, cmp2> q2;//维护f[i]+sum[i] 
signed main(){
    init();
    scanf("%lld", &n);
    for(int i = 1; i <= n; i ++){
        int u, v, c;
        scanf("%lld%lld%lld", &u, &v, &c);
        insert(u, v, c);
        insert(v, u, c);
    }
    findring(1);
    memset(fa, 0, sizeof fa);
    for(int i = 1; i <= cnt; i ++) dfs(ring[i]);//对于环上的每个点向外延伸的树做一次dp 
    ring[0] = ring[cnt]; 
    for(int i = 1; i <= cnt; i ++){//把环的边权表示成序列的形式 
        int u = ring[i];
        for(int j = p[u]; j + 1; j = e[j].nxt){
            int v = e[j].v;
            if(v == ring[i - 1]) val[i] = e[j].c;
        }
    }
    for(int i = 1; i <= cnt;i ++) val[i + cnt] = val[i], ring[i + cnt] = ring[i];//拆环成链 

    for(int i = 1; i <= cnt * 2; i ++) sum[i] = sum[i - 1] + val[i];//做环上边权的前缀和 
    int anss = 1e18;
    for(int i = 1; i <= cnt; i ++) q1.push(i), q2.push(i);
    for(int i = 1; i <= cnt; i ++){
        while(q1.top() < i && q1.size() > 1)	q1.pop();
        while(q2.top() < i && q2.size() > 1)	q2.pop();
        int x = q1.top(), y = q2.top();
        if(x == y){//要靠考虑i == j的情况,每个再取出一个最大值,然后交叉组合看那个大 
            q1.pop(), q2.pop();
            while(q1.top() < i && q1.size() > 1)	q1.pop();
            while(q2.top() < i && q2.size() > 1)	q2.pop();
            int xx = q1.top(), yy = q2.top();
            anss = min(anss, max(f[ring[x]] - sum[x] + f[ring[yy]] + sum[yy], f[ring[xx]] - sum[xx] + f[ring[y]] + sum[y]));//硬套刚刚的公式 
            q1.push(x), q2.push(y);
        }else anss = min(anss, f[ring[x]] - sum[x] + f[ring[y]] + sum[y]);//不然就直接搞 
        q1.push(cnt + i), q2.push(cnt + i);
    }
    ans = max(anss, ans);//和之前树的直径求个最大值 
    
    printf("%lld.%lld", ans/2, (ans&1)? 5:0);//输出 
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38944163/article/details/89470928