NOI2013快餐店【图上找环+线段树】

NOI 2013 快餐店

NOI 线段树


题目传送点

说白了,就是给个N个点N条边的图,然后求一个点(不一定是给的那N个点),到所有给定的点的路程最大值最小

=≡Σ((( つ•̀ω•́)つ我是蒟蒻,想了好久的二分和三分(最大值最小嘛)


题解

[朴素的想法]

我们可以先假设:如果给的是树而不是图呢?
树就很简单, O(n) 求出树的直径(不会点这里),因为要求最大值最小嘛,非常显然,直径的一半就是答案。
现在呢,给你一个图,怎么办,想这种树好做图不好做的可以把图尽量向树的方向靠拢,现在N个点,N条边,也就是说是一棵外向树(一个环,一些环的定点连着很多子树),那么我们就可以求出这个环(假设是 K 条边的环),然后枚举这个环上哪一条边不走,然后再做 K 次找树的直径即可。
复杂度为: O(N2) ,预计得分60

[满分算法]

当然要先把环求出来(如果直接求图的“直径”会出问题,自己可以想个反例),依然要枚举那一条边不选,现在的问题就是怎样快速处理
因为是个环,方便写程序有时也降低复杂度,我们把环拆开(像沙子合并那道DP一样,乘个2倍,然后拉开),那么现在就是一条链,链上一些点向下连了一棵子树(自己脑补吧ㄟ(▔,▔)ㄏ,ubuntu下画个简图真是麻烦)
那么我们假设链上 K 个点,分别为 1,2,3 ,然后我们预处理出从链上这个点到子树的最长的路径记为 Di ,链上每两个点之间的距离为 disij ,根据两点间的距离求出每个点到第一个点的距离前缀和记做 Si ,那么第 i 个点的子树到第 j 的点的子树的最大距离为 SjSj+Di+Dj ,我们再移动几项: DiSi+Dj+Sj ,所以用线段树维护一个[L, R]中 DiSi+Dj+Sj 的最大值,用 O(log2N) 的时间得到答案。
那么我们 N 次枚举, ANS=min(Query(i,i+K1))(i[1,K])
但是这还没有完,如果最长路并没有经过环呢(我们在用线段树维护的时候,维护的最小单元不是一个点,而是区间 [i,i+1] ),所以,我们再和链上 K 个点的子树中的直径求 min 即可(想想为什么不是 max )。
复杂度: O(Nlog2N) ,预计得分100

讲完辣 (〃^∇^)ぇ∧∧∧っ

最后附上代码

#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef long long lld;

const int maxn = 1e5 + 13, maxm = maxn * 2;
const lld INF = 0x7fffffffffffffffll / 2ll;
int n, pos, head[maxm], used[maxn];
int c[maxn], size;
lld dis[maxm], s[maxm], d[maxm];
int l[maxm * 2], r[maxm * 2], rs[maxm * 2], ls[maxm * 2], root;
lld v1[maxm * 2], v2[maxm * 2], v[maxm * 2], maxSum;
queue<int>Q;
struct node {
    int v, w, last;
}line[maxn * 2];

//超级读数
void read(int &a) {
    a = 0;
    bool judge = false;
    char c;
    while((c = getchar()) != EOF) {
        if(c == ' ' || c == '\n') {
            if(!judge) continue;
            return;
        }
        a = a * 10 + (c - '0');
        judge = true;
    }
}

//链表存图
void my_read(int a, int b, int c) {
    line[++pos] = (node) {b, c, head[a]};
    head[a] = pos;
}

//深搜找环
bool circleDfs(int pre, int u) {
    used[u] = true;
    for(int i = head[u]; i; i = line[i].last) {
        const int v = line[i].v;
        if(v == pre) continue;
        if(used[v]) {
            c[++size] = u;
            //s点保存前缀和
            s[size] = (lld)line[i].w;
            used[v] = false;
            return true;
        }
        bool judge = circleDfs(u, v);
        if(judge) {
            c[++size] = u;
            s[size] = s[size - 1] + (lld)line[i].w;
            if(used[u]) return true;
            return false;
        }
    }
    return false;
}

void initForCircle() {
    circleDfs(1, 1);
    memset(used, 0, sizeof(used));
    for(int i = 1; i <= size; i++) used[c[i]] = true;
}

//对链上每个点到树上求最长路径
lld getFarthestDis(int pre, int u) {
    lld far = 0ll;
    for(int i = head[u]; i; i = line[i].last) {
        const int v = line[i].v;
        if(v == pre || used[v]) continue;
        far = max(far, getFarthestDis(u, v) + (lld)line[i].w);
    }
    return far;
}

//线段树标准模板
void builtTree(int &i, int A, int B) {
    i = ++pos;
    l[i] = A; r[i] = B;
    //
    //最小单元应该是一个长度为1的区间
    if(A + 1 == B) {
        v1[i] = d[A] - s[A];
        v2[i] = d[B] + s[B];
        v[i] =  v1[i] + v2[i];
        return;
    }
    int mid = (A + B) >> 1;
    builtTree(ls[i], A, mid);
    builtTree(rs[i], mid, B);
    int LS = ls[i], RS = rs[i];
    v1[i] = max(v1[LS], v1[RS]);
    v2[i] = max(v2[LS], v2[RS]);
    v[i] = max(v[LS], v[RS]);
    v[i] = max(v[i], v1[LS] + v2[RS]);
}

lld Query(int i, int A, int B) {
    int L = l[i], R = r[i];
    if(B <= L || A >= R) return 0;
    if(A <= L && R <= B) {
        lld temp;
        temp = max(v[i], maxSum + v2[i]);
        maxSum = max(v1[i], maxSum);
        return temp;
    }
    return max(Query(rs[i], A, B), Query(ls[i], A, B));
}

int tot, temp[maxn];

//找直径
int findDiameter(int pre, int u) {
    temp[++tot] = u;
    for(int i = head[u]; i; i = line[i].last) {
        const int v = line[i].v;
        if(v == pre || used[v]) continue;
        dis[v] = dis[u] + (lld)line[i].w;
        findDiameter(u, v);
    }
}

//求每棵子树的直径
lld findLongestRoad(int x) {
    used[x] = false;
    int A; lld max1;

    tot = 0;
    dis[x] = 0;
    findDiameter(x, x);
    A = 0; max1 = 0ll;
    for(int i = 1; i <= tot; i++)
        if(max1 < dis[temp[i]]) max1 = dis[temp[i]], A = temp[i];

    for(int i = 1; i <= tot; i++) dis[temp[i]] = INF;

    tot = 0;
    dis[A] = 0;
    findDiameter(A, A);
    A = 0; max1 = 0ll;
    for(int i = 1; i <= tot; i++)
        if(max1 < dis[temp[i]]) max1 = dis[temp[i]];

    used[x] = true;
    return max1;
}

int main() {
    freopen("foodshop.in", "r", stdin);
//  freopen("test.in", "w", stdout);

    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        int a, b, c;
        read(a); read(b); read(c);
        my_read(a, b, c);
        my_read(b, a, c);
    }

    initForCircle();

    for(int i = 1; i <= size; i++) dis[c[i]] = getFarthestDis(c[i], c[i]);

    for(int i = 1; i <= size; i++) {
        d[i] = dis[c[i]]; d[i + size] = d[i];
        s[i + size] = s[size] + s[i];
    }
    pos = 0;
    builtTree(root, 1, size * 2);
    lld min1 = INF;
    for(int i = 1; i <= size; i++) {
        maxSum = -INF;
        lld temp = Query(root, i, i + size - 1);
        min1 = min(min1, temp);
    }
    for(int i = 1; i <= n; i++) dis[i] = INF;
    for(int i = 1; i <= size; i++) min1 = max(min1, findLongestRoad(c[i]));

    printf("%.1lf\n", (double)min1 / 2.0);

    return 0;
}

你可能感兴趣的:(线段树)