[IOI2008]Island(基环树的直径+优化)
其实是一道很坑的模板题
题意很简单——给定几个基环树的森林,求每棵基环树的直径长度之和
基环树的直径有两种情况
- 不经过环的,每一棵子树的直径中最长的
- 经过环的,环上的一部分加上对应的两颗子树的深度
如图:
对于第一种情况的话,计算出每棵树的直径求一个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;
}