[IOI2008] Island
算法
题意可以转化成
给定一个基环树森林, 求每颗基环树上的直径长度之和
找环
按照基环树的方法找即可
求直径
(i) 直径不经过环
对于以环上每一个点的子树, 记录直径即可
(ii) 直径经过环
关于为什么需要断环为链:
方便快速处理环上两点间的距离, 显然不复制两份正确性会有问题
本质上是处理优弧劣弧的 trick
最后的答案即为 ( \(dis\) 为环上距离前缀和, \(D\) 为子树直径, \(Size\) 为环长)
\[\max{(dis_i - dis_j + D_i + D_j, i - j + 1 \leq Size)}
\]
\[\Downarrow
\]
\[\max{(D_i + dis_i + (D_j - dis_j), i - j + 1 \leq Size)}
\]
可以使用单调队列, 类似滑动窗口 dp 的方式优化
代码实现细节
这题感觉不太好打, 在这里理一下思路
先用并查集标记一下不同的基环树(毕竟是森林)
全图跑一遍无向图上的拓扑排序
找到环之后, 对于每个环(即每棵基环树), 计算以环上的每一点为根的树的直径, 即 \(D_i\)
在计算的同时, 用 bfs 遍历这个环将其化成链
这个可以用网络流里那种反向边 \(\oplus 1\) 的方法解决(感谢伟大 洛谷题解 的思路)
预处理一下 \(dis\) 数组
对于每一棵树, 答案累加
代码
后补
#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e6 + 20;
int N;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
x = x * 10 + ch - '0', ch = getchar();
return x * f;
}
class Graph_Class
{
private:
public:
/*链式前向星*/
struct Edge_node
{
int to, w;
int next;
} Edge[MAXN << 1];
int Edge_cnt = -1;
int head[MAXN];
void head_init()
{
for (int i = 1; i <= N; i++)
head[i] = -1;
}
/*并查集*/
struct Graph_Dsu
{
int fa[MAXN];
void init() {
for (int i = 1; i <= N; i++)
fa[i] = i;
}
int find(int x) { return fa[x] = (x == fa[x]) ? x : find(fa[x]); }
void merge(int u, int v)
{
int fa_u = find(u), fa_v = find(v);
if (fa_u != fa_v)
fa[fa_u] = fa_v;
}
} Dsu;
/*-----------------------------------------------------------------------------------------------------------------------------*/
int Graph_Degree[MAXN];
/*所需的初始化*/
void init() { head_init(), Dsu.init(); }
/*加边函数*/
void addedge(int u, int v, int w)
{
Edge[++Edge_cnt].to = v;
Edge[Edge_cnt].w = w;
Edge[Edge_cnt].next = head[u];
head[u] = Edge_cnt;
Dsu.merge(u, v);
Graph_Degree[u]++;
}
} Graph;
int Ans = 0;
class Doit_Class
{
private:
/*单调队列*/
struct MonoQueue_Struct
{
struct node
{
int Val;
int Pos;
};
std::deque<node> q;
void insert(node x, int Size)
{
/*去尾*/
while(!q.empty() && x.Val > q.back().Val)
q.pop_back();
q.push_back(x);
}
void update(int Now, int Size)
{
/*删头*/
while (!q.empty() && Now - q.front().Pos + 1 > Size) // 是否需要 i - j + 1 值得思考
q.pop_front();
}
} MonoQueue;
public:
/*全图上的 拓扑排序, 最后没有进过队的就是环上的点*/
bool OnCir[MAXN];
std::queue<int> Q;
void topo(int NowGraph)
{
while(!Q.empty()) Q.pop(); //
for(int i = 1; i <= N; i++) {
if(Graph.Dsu.find(i) != NowGraph) {
OnCir[i] = false;
}
else { //
OnCir[i] = true;
}
}
/*注意无向图上的实现差别*/
for (int i = 1; i <= N; i++)
if (Graph.Graph_Degree[i] == 1 && Graph.Dsu.find(i) == NowGraph)
Q.push(i), OnCir[i] = false;
while(!Q.empty())
{
int Now = Q.front();
Q.pop();
OnCir[Now] = false;
for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next)
{
int NowTo = Graph.Edge[i].to, NowW = Graph.Edge[i].w;
if (!OnCir[NowTo] || Graph.Dsu.find(NowTo) != NowGraph)
continue;
if(--Graph.Graph_Degree[NowTo] == 1)
Q.push(NowTo), OnCir[NowTo] = false;
}
}
}
struct Cir
{
int Now;
int Nxt, w;
} List[MAXN << 1];
int List_cnt = 0;
int D[MAXN], Dis[MAXN];
bool Vis_Cir[MAXN << 1]; // 记录边的访问情况
int List_St = -1;
/*化环为链 + 计算子树直径*/
void FindCir(int NowGraph, int Now)
{
for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next)
{
int NowTo = Graph.Edge[i].to, NowW = Graph.Edge[i].w;
/*不要反向边我们共建新香港*/
if(Vis_Cir[i ^ 1] || Graph.Dsu.find(NowTo) != NowGraph || !OnCir[NowTo])
continue;
if(NowTo == List_St) {
List[++List_cnt].Now = Now;
List[List_cnt].Nxt = NowTo;
List[List_cnt].w = NowW;
Vis_Cir[i] = true;
return;
}
List[++List_cnt].Now = Now;
List[List_cnt].Nxt = NowTo;
List[List_cnt].w = NowW;
Vis_Cir[i] = true;
FindCir(NowGraph, NowTo);
}
}
/*计算链上距离前缀和*/
void CalcDis()
{
for (int i = 1; i <= List_cnt; i++) {
List[List_cnt + i].Now = List[i].Now;
List[List_cnt + i].Nxt = List[i].Nxt;
List[List_cnt + i].w = List[i].w;
}
for (int i = 1; i <= List_cnt * 2; i++)
Dis[i] = Dis[i - 1] + List[i].w;
}
int NowD = 0;
/*树形 dp*/
void CalcD(int Now, int fa, int NowGraph)
{
for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next)
{
int NowTo = Graph.Edge[i].to, NowW = Graph.Edge[i].w;
if (NowTo == fa || Graph.Dsu.find(NowTo) != NowGraph || OnCir[NowTo])
continue;
CalcD(NowTo, Now, NowGraph);
NowD = std::max(NowD, D[Now] + D[NowTo] + Graph.Edge[i].w);
D[Now] = std::max(D[Now], D[NowTo] + Graph.Edge[i].w);
}
}
int CalcAns()
{
int NowAns = 0;
while(!MonoQueue.q.empty()) MonoQueue.q.pop_front();
/*当前点 i*/
MonoQueue.insert((MonoQueue_Struct::node){D[List[1].Now], 0}, List_cnt);
for (int i = 1; i <= List_cnt * 2 - 1; i++)
{
if(MonoQueue.q.empty()) continue;
MonoQueue.update(i, List_cnt);
NowAns = std::max(NowAns, D[List[i].Nxt] + Dis[i] + MonoQueue.q.front().Val);
MonoQueue.insert((MonoQueue_Struct::node){D[List[i].Nxt] - Dis[i], i}, List_cnt);
}
return NowAns;
}
int doit(int NowGraph)
{
int Ans_withoutroot = 0, Ans_withroot = 0; // 答案
/*找环*/
topo(NowGraph);
/*化链*/
List_St = -1;
for (int i = 1; i <= N; i++)
if(OnCir[i] && Graph.Dsu.find(i) == NowGraph) {
List_St = i;
break;
}
List_cnt = 0;
// memset(Vis_Cir, false, sizeof(Vis_Cir)); // 可以删去
FindCir(NowGraph, List_St);
/*--------------------------------------------------------------------------------------------------------------------*/
/*计算链上距离前缀和*/
CalcDis();
/*计算 D*/
memset(D, 0, sizeof(D));
for (int i = 1; i <= List_cnt; i++)
NowD = 0, CalcD(List[i].Now, -1, NowGraph), Ans_withoutroot = std::max(Ans_withoutroot, NowD);
Ans_withroot = CalcAns();
return std::max(Ans_withoutroot, Ans_withroot);
}
bool Vis[MAXN];
void solve()
{
/*对于每一棵基环树, 进行处理*/
for (int i = 1; i <= N; i++) {
int NowGraph = Graph.Dsu.find(i);
if (!Vis[NowGraph])
Ans += doit(NowGraph), Vis[NowGraph] = true; //
}
printf("%lld", Ans);
}
} Doit;
signed main()
{
N = read();
Graph.init();
for (int i = 1; i <= N; i++) {
int u, w;
u = read(), w = read();
Graph.addedge(i, u, w), Graph.addedge(u, i, w);
}
Doit.solve();
return 0;
}
/*
7
3 8
7 2
4 2
1 4
1 9
3 4
2 3
24
*/
总结
基环树板子 + 复杂的环上处理, 单调队列的应用