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

来源:互联网 发布:java仓库管理 编辑:程序博客网 时间:2024/06/11 17:38

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 <iostream>#include <cstdlib>#include <cstdio>#include <cstring>#include <queue>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;}
2 0
原创粉丝点击