【IOI 2008】岛屿(基环外向树)

题目链接

【IOI 2008】岛屿


题目大意

给定一个基环树森林,求它们的直径之和。


题解

基环外向树的直径分两类:
1.在某一棵外向树内部(某个外向树的直径)。
2.一个端点在一棵外向树内,横跨环上的一段区间,另一个端点在另一颗外向树内。

首先,我们找到基环,然后对于每一个外向树都求出能到子树中可以延伸的最长的距离 pathi p a t h i ,顺便求出外向树的直径,更新答案。
接着只需要考虑第二类情况了。任取一个节点 point p o i n t ,记环上第 i i 个节点离point的距离为 disti d i s t i ,基环上边的总距离为 sum s u m ,那么答案为:

max{pathi+(distidistj)+pathj,pathi+(sum(distidistj))+pathj,} m a x { p a t h i + ( d i s t i − d i s t j ) + p a t h j , p a t h i + ( s u m − ( d i s t i − d i s t j ) ) + p a t h j , }

变形后可以得到:
max{(disti+pathi)(distjpathj),sum(distipathi)+(distj+pathj)} m a x { ( d i s t i + p a t h i ) − ( d i s t j − p a t h j ) , s u m − ( d i s t i − p a t h i ) + ( d i s t j + p a t h j ) }

注意: (disti+pathi) ( d i s t i + p a t h i ) 是可以事先算出来的。
于是通过一些简单的技巧我们就可以通过此题了。


代码

从这份代码中,大家可以看到我做题时经历的坎坷:

/*
这边使用了错误的动态规划,得分36pts
#include <cstdio>
#include <iostream>
typedef long long ll;
using namespace std;
const int maxn = 1000005;
const int maxm = 2000005;
bool vis[maxn], mrk[maxn];
int n, road[maxn][2];
int edge, ter[maxn], len[maxn], nxt[maxn], lnk[maxn];
int circle, head, tail, id[maxm], par[maxn], node[maxm];
ll ans, res, sum[maxm], path[maxn], val[maxm];
void addedge(int u, int v, int w) {
    ter[++edge] = v;
    len[edge] = w;
    nxt[edge] = lnk[u];
    lnk[u] = edge;
}
void tree_diameter(int u) {
    mrk[u] = 1;
    for (int i = lnk[u]; i; i = nxt[i]) {
        int v = ter[i], w = len[i];
        if (mrk[v]) {
            continue;
        }
        tree_diameter(v);
        res = max(res, path[u] + path[v] + w);
        path[u] = max(path[u], path[v] + w);
    }
}
void find_circle(int u) {
    circle = 0, vis[u] = 1;
    for (int v; ; u = v) {
        v = road[u][0];
        if (vis[v]) {
            mrk[v] = 1, node[++circle] = v, sum[1] = road[v][1];
            for (int i = u; i != v; i = par[i]) {
                mrk[i] = 1, node[++circle] = i, sum[circle] = road[i][1];
            }
            for (int i = 1; i <= circle; i++) {
                node[i + circle] = node[i];
                sum[i + circle] = sum[i];
            }
            for (int i = 1; i <= circle << 1; i++) {
                sum[i] += sum[i - 1];
            }
            break;
        }
        vis[v] = 1;
        par[v] = u;
    }
}
ll solve(int u) {
    res = 0;
    find_circle(u);
    for (int i = 1; i <= circle; i++) {
        tree_diameter(node[i]);
    }
    head = tail = 0;
    for (int i = 1; i <= circle << 1; i++) {
        pop(i - circle);
        res = max(res, sum[i] + path[node[i]] - query());
        push(i, sum[i] - path[node[i]]);
    }
    return res;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d %d", &road[i][0], &road[i][1]);
        addedge(road[i][0], i, road[i][1]);
    }
    for (int i = 1; i <= n; i++) {
        if (!mrk[i]) {
            ans += solve(i);
        }
    }
    printf("%lld\n", ans);
    return 0;
}
*/
/*
这边使用了暴力,得分72pts
#include <cstdio>
#include <iostream>
typedef long long ll;
using namespace std;
const int maxn = 1000005;
const int maxm = 2000005;
bool vis[maxn], mrk[maxn];
int n, road[maxn][2];
int edge, ter[maxn], len[maxn], nxt[maxn], lnk[maxn];
int circle, head, tail, id[maxm], par[maxn], node[maxm];
ll ans, res, sum[maxm], path[maxn], val[maxm];
void push(int nid, int nval) {
    while (head < tail && val[tail - 1] >= nval) {
        tail--;
    }
    id[tail] = nid;
    val[tail++] = nval;
}
void pop(int pid) {
    while (head < tail && id[head] <= pid) {
        head++;
    }
}
ll query() {
    return val[head];
}
void addedge(int u, int v, int w) {
    ter[++edge] = v;
    len[edge] = w;
    nxt[edge] = lnk[u];
    lnk[u] = edge;
}
void tree_diameter(int u) {
    mrk[u] = 1;
    for (int i = lnk[u]; i; i = nxt[i]) {
        int v = ter[i], w = len[i];
        if (mrk[v]) {
            continue;
        }
        tree_diameter(v);
        res = max(res, path[u] + path[v] + w);
        path[u] = max(path[u], path[v] + w);
    }
}
void find_circle(int u) {
    circle = 0, vis[u] = 1;
    for (int v; ; u = v) {
        v = road[u][0];
        if (vis[v]) {
            mrk[v] = 1, node[++circle] = v, sum[1] = road[v][1];
            for (int i = u; i != v; i = par[i]) {
                mrk[i] = 1, node[++circle] = i, sum[circle] = road[i][1];
            }
            for (int i = 1; i <= circle; i++) {
                node[i + circle] = node[i];
                sum[i + circle] = sum[i];
            }
            for (int i = 1; i <= circle << 1; i++) {
                sum[i] += sum[i - 1];
            }
            break;
        }
        vis[v] = 1;
        par[v] = u;
    }
}
ll solve(int u) {
    res = 0;
    find_circle(u);
    for (int i = 1; i <= circle; i++) {
        tree_diameter(node[i]);
    }
    for (int i = 1; i <= circle; i++) {
        for (int j = 1; j < i; j++) {
            res = max(res, max((sum[i] + path[node[i]]) - (sum[j] - path[node[j]]), sum[circle] - (sum[i] - path[node[i]]) + (sum[j] + path[node[j]])));
        }
    }
    return res;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d %d", &road[i][0], &road[i][1]);
        addedge(road[i][0], i, road[i][1]);
    }
    for (int i = 1; i <= n; i++) {
        if (!mrk[i]) {
            ans += solve(i);
        }
    }
    printf("%lld\n", ans);
    return 0;
}
*/
//下面就是正解了,得分100pts
#include <cstdio>
#include <iostream>
typedef long long ll;
using namespace std;
const int maxn = 1000005;
bool vis[maxn], mrk[maxn];
int n, road[maxn][2];
int edge, ter[maxn], len[maxn], nxt[maxn], lnk[maxn];
int circle, par[maxn], node[maxn];
ll mn, mx, ans, res, sum[maxn], path[maxn];
inline int read() {
    int ret = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        ch = getchar();
    }
    while (isdigit(ch)) {
        ret = (ret << 1) + (ret << 3) + ch - '0';
        ch = getchar();
    }
    return ret;
}
inline void addedge(int u, int v, int w) {
    ter[++edge] = v;
    len[edge] = w;
    nxt[edge] = lnk[u];
    lnk[u] = edge;
}
void find_circle(int u) {
    circle = 0, vis[u] = 1;
    for (int v; ; u = v) {
        v = road[u][0];
        if (vis[v]) {
            mrk[v] = 1, node[++circle] = v, sum[1] = road[v][1];
            for (int i = u; i != v; i = par[i]) {
                mrk[i] = 1, node[++circle] = i, sum[circle] = road[i][1];
            }
            for (int i = 1; i <= circle; i++) {
                sum[i] += sum[i - 1];
            }
            break;
        }
        vis[v] = 1;
        par[v] = u;
    }
}
void tree_diameter(int u) {
    mrk[u] = 1;
    for (int i = lnk[u]; i; i = nxt[i]) {
        int v = ter[i], w = len[i];
        if (mrk[v]) {
            continue;
        }
        tree_diameter(v);
        res = max(res, path[u] + path[v] + w);
        path[u] = max(path[u], path[v] + w);
    }
}
ll solve(int u) {
    mn = 1e15, mx = -1e15, res = 0;
    find_circle(u);
    for (int i = 1; i <= circle; i++) {
        tree_diameter(node[i]);
    }
    for (int i = 1; i <= circle; i++) {
        res = max(res, max((sum[i] + path[node[i]]) - mn, sum[circle] - (sum[i] - path[node[i]]) + mx));
//      printf("%lld %d\n", res, i);
        mn = min(mn, sum[i] - path[node[i]]);
        mx = max(mx, sum[i] + path[node[i]]);
    }
    return res;
}
int main() {
    n = read();
    for (int i = 1; i <= n; i++) {
        road[i][0] = read(), road[i][1] = read();
        addedge(road[i][0], i, road[i][1]);
    }
    for (int i = 1; i <= n; i++) {
        if (!mrk[i]) {
            ans += solve(i);
        }
    }
    printf("%lld\n", ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值