[IOI2008]Island(基环树的直径+优化)

版权声明:蒟蒻的博文,dalao转载标明出处就好吖 https://blog.csdn.net/jokingcoder/article/details/88317946

原题地址

其实是一道很坑的模板题

题意很简单——给定几个基环树的森林,求每棵基环树的直径长度之和

基环树的直径有两种情况

  1. 不经过环的,每一棵子树的直径中最长的
  2. 经过环的,环上的一部分加上对应的两颗子树的深度

如图:

在这里插入图片描述

对于第一种情况的话,计算出每棵树的直径求一个max就好了
第二种情况可能相对麻烦一点。。。

其实可以把以环上的点为根的所有子树的深度求出来,然后再在环上跑用单调队列维护的dp就好。
具体操作的话可以断环成链,再复制一倍的链长就可以了


这边才是重点

(手动滑稽
然鹅本题的坑点在于数据。。。第一次TLE,MLE什么的在正常不过了
首先是因为本题的数据范围是1e6,一不小心就会常数爆炸
MLE呢,是因为他会故意卡你dfs,然后爆栈(但是也有打迪法师没爆炸的神仙)

而我向来自带大常数和大内存…
第一遍的代码WA飞,T飞,还炸了内存…(还有丑飞

#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 1000010
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;
typedef long long LL;
struct Node{
    int to, nxt;
    LL len;
}e[N << 1];
struct Q{
    int x;
    LL v;
}q[N << 1];

int lst[N], fa[N], used[N], vis[N], hoop[N], hp[N << 1], des[N], cnt;
LL dp[N], hp_len[N << 1], sum_len[N << 1], ans;

inline void add(int u, int v, LL len) {
    e[++cnt].to = v;
    e[cnt].nxt = lst[u];
    e[cnt].len = len;
    lst[u] = cnt;
}

inline int gfa(int x) {
    if (fa[x] == x) return x;
    fa[x] = gfa(fa[x]);
    return fa[x];
}

inline void merge(int x, int y) {
    int f1 = gfa(x), f2 = gfa(y);
    if (f1 != f2) {
        if (f1 > f2) swap(f1, f2);
        fa[f2] = f1;
    }
}

inline int find_hoop(int x, int fa) {
    if (vis[x]) {
        hoop[x] = 1;
        return 1;
    }
    vis[x] = 1;
    for (int i = lst[x]; i; i = e[i].nxt) {
        int son = e[i].to;
        if (!(son != fa || des[x] == son && des[son] == x)) continue;
        int k = find_hoop(son, x);
        if (k == 1) {
            hp[++hp[0]] = x;
//			printf("%d\n", x);
            hp_len[hp[0]] = e[i].len;
            if (hoop[x]) return 2;
            else hoop[x] = 1;
            return 1;
        }
        if (k == 2) return 2;
    }
    return 0;
}

inline void tree_dp(int x, int fa) {
    for (int i = lst[x]; i; i = e[i].nxt) {
        int son = e[i].to;
        if (son == fa || hoop[son]) continue;
        tree_dp(son, x);
        ans = max(ans, dp[x] + dp[son] + e[i].len);
        dp[x] = max(dp[x], dp[son] + e[i].len);
    }
}

inline LL make_ans(int x) {
//	printf("%d\n", x);
    memset(hp, 0, sizeof hp);
    memset(dp, 0, sizeof dp);
    memset(q, 0, sizeof q);
    find_hoop(x, x);
    LL ans = 0;
    for (int i = 1; i <= hp[0]; ++i) {
        tree_dp(hp[i], hp[i]);
        ans = max(ans, dp[hp[i]]);
    }
    for (int i = 1; i <= hp[0]; ++i) {
        hp[i + hp[0]] = hp[i];
        hp_len[i + hp[0]] = hp_len[i];
    }
//	for (int i = 1; i <= hp[0] * 2; ++i) printf("%d ", hp[i]);
//	puts("");
//	for (int i = 1; i <= hp[0] * 2; ++i) printf("%d ", hp_len[i]);
    int h = 1, t = 0;
    q[0].v = INF;
    for (int i = 1; i <= hp[0] * 2; ++i) {
        while (i - q[h].x >= hp[0]) ++h;
        q[h - 1].v = INF;
        sum_len[i] = sum_len[i - 1] + hp_len[i];
//		printf("~~%d~~\n", q[h].x);
        LL val = dp[hp[i]] - sum_len[i];
        while (q[t].v <= val) --t;
        q[++t].v = val;
//		printf("%d\n", q[t].v);
        q[t].x = i;
        ans = max(ans, sum_len[i] - sum_len[q[h].x] + dp[hp[q[h].x]] + dp[hp[i]]);
    }
    return ans;
}

int main() {
    int n;
    LL l;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) fa[i] = i;
    for (int i = 1; i <= n; ++i) {
        scanf("%d%lld", &des[i], &l);
        add(des[i], i, l);
        add(i, des[i], l);
        merge(i, des[i]);
    }
    for (int i = 1; i <= n; ++i)
        fa[i] = gfa(i);
    LL ansall = 0;
    for (int i = 1; i <= n; ++i) {
        if (!used[fa[i]]) {
            ansall += make_ans(fa[i]);
//	printf("%lld\n", ansall);
//			puts("-----------------");
            used[fa[i]] = 1;
        }
    }
    printf("%lld\n", ansall);
    return 0;
}

经过一晚上的思路整理,emm发现本题基环树的读入有一个很好的地方
我们可以把每组输入钦定为有向边,然后显然最后的形态一定是这样的:

在这里插入图片描述

用拓扑排序就能很快把非环的部分筛掉
(我用了个并查集来维护了每棵基环树,其实貌似也不需要的样子
最后一样的在环上跑dp就可以了

AC代码非常非常精炼(因为压行了…

#include <cstdio>
#include <algorithm>
#define N 1000010

using namespace std;
typedef long long LL;
struct Node { LL v; int x; } q[N << 1];
int fa[N], des[N], din[N], quq[N], vis[N]; LL len[N], ans[N], d[N];
inline int gfa(int x) { if (fa[x] == x) return x; fa[x] = gfa(fa[x]); return fa[x]; }

inline void merge(int x, int y) {
	register int fx = gfa(x), fy = gfa(y);
	if (fx != fy) { if (fx > fy) swap(fx, fy); fa[fy] = fx; }
}

int main() {
	int n; scanf("%d", &n);
	for (register int i = 1; i <= n; ++i) fa[i] = i;
	for (register int i = 1; i <= n; ++i) {
		scanf("%d%lld", &des[i], &len[i]); din[des[i]]++;
	}
	register int h = 0, t = 0;
	for (register int i = 1; i <= n; ++i) if (!din[i]) quq[++t] = i;
	while (h < t) {//拓扑
		++h; register int pt = des[quq[h]];
		ans[pt] = max(max(ans[quq[h]], ans[pt]), d[pt] + d[quq[h]] + len[quq[h]]);
		d[pt] = max(d[pt], d[quq[h]] + len[quq[h]]);
		din[pt]--; if (!din[pt]) quq[++t] = pt;
	}
	for (int i = 1; i <= n; ++i) if (din[i]) merge(i, des[i]);
	for (register int i = 1; i <= n; ++i) fa[i] = gfa(i);
	LL ansall = 0;
	for (register int i = 1; i <= n; ++i) {
		if (din[i] && !vis[fa[i]]) {
			vis[fa[i]] = 1;
			register int j = fa[i], h = 1, t = 0; LL sum = 0, val, anss = 0;
			do {
				sum += len[j]; j = des[j]; anss = max(anss, ans[j]);
				if (t) anss = max(anss, q[1].v + sum + d[j]);
				val = d[j] - sum; while (t > 0 && q[t].v <= val) --t;
				q[++t].v = val; q[t].x = j;
			} while (j != fa[i]);
			do {//两倍链长
				sum += len[j]; j = des[j];
				if (q[h].x == j) ++h; if (t) anss = max(anss, q[h].v + sum + d[j]);
				val = d[j] - sum; while (t >= h && q[t].v <= val) --t;
				q[++t].v = val; q[t].x = j;
			} while (j != fa[i]);
			ansall += anss;
		}
	}
	printf("%lld\n", ansall);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/jokingcoder/article/details/88317946
今日推荐