为什么我感觉所有树上简单问题到了基环树上都成了毒瘤……
Solution [IOI2008]Island
题目大意:给定基环树森林,求每棵树的直径之和
动态规划-树形dp,单调队列,基环树
分析:
首先如果把"基环"去掉就是sb题
我们用\(f[u]\)表示以\(u\)为根的子树的最长链,假定有边\(e\)
\(f[u]=max\{f[e.to]+e.dist\}\)
用\(ans[u]\)表示以\(u\)为根的子树的直径
\(ans[u]=max\{ans[e.to],f'[u] + f[e.to]+e.dist\}\)
一棵树直径有两种可能,它子树的直径和过它根的直径,由子树\(ans\)转移不解释,\(f'\)代表更新前的\(f\),我们用之前子树的最长链加上当前子树的最长链得到第二种情况
树形\(dp\)可以\(O(n)\)解决
然后在基环树上,直径分两种,不经过环的直径和经过环的直径
不经过环的直径我们在环上点的\(ans\)中找个\(max\)即可
如果我们要求经过环的直径,假定\(u,v\)均在环上,显然\(ans=max\{f[u]+f[v]+dis(u,v)\}\),其中\(dis(u,v)\)为环上两点两条路径中较长的那条
这玩意儿如果暴力是\(O(n^3)\)的,加上前缀和可以做到\(O(n^2)\)
由于同时考虑两个方向不好做,我们断环成链
\(ans=max\{f[u]+f[v]+sum[u]-sum[v]\ \quad v<u\}\),我们把环上距离做个前缀和得到\(sum\),然后这个东西就可以用单调队列来优化到\(O(n)\)
窝学艺不精导致的玄学\(bug:\)
\(0\)小于一个负数
问题:使用了\(vector\)所致,\(vector.size()\)返回值\(size\_t\),该类型为无符号整数,然后有符号数和无符号数运算会统一成无符号数运算……无符号数存负数就会\(GG\)
\(Trick:\)题目保证每个点出度为\(1\),如果我们建反图就是一棵外向树
#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 1e6 + 100;
typedef long long ll;
inline int read(){
int x = 0;char c = getchar();
while(!isdigit(c))c = getchar();
while(isdigit(c))x = x * 10 + c - '0',c = getchar();
return x;
}
struct Edge{int to,dist;};
vector<Edge> G[maxn],rG[maxn];
vector<int> cir[maxn];
int deg[maxn],vis[maxn],iscir[maxn],q[maxn << 1],head,tail,n,cir_tot;
ll f[maxn],ans[maxn],sum[maxn << 1],val[maxn << 1],ansout;
inline void dfs(int u){
for(auto e : G[u]){
if(iscir[e.to])continue;
dfs(e.to);
ans[u] = max(ans[u],f[u] + f[e.to] + e.dist);
ans[u] = max(ans[u],ans[e.to]);
f[u] = max(f[u],f[e.to] + e.dist);
}
}
inline void topo(){
queue<int> Q;
for(int i = 1;i <= n;i++){
iscir[i] = 1;
if(!deg[i])Q.push(i);
}
while(!Q.empty()){
int u = Q.front();Q.pop();
iscir[u] = 0;
for(auto e : rG[u])
if(!--deg[e.to])Q.push(e.to);
}
for(int i = 1;i <= n;i++)
if(iscir[i] && !vis[i]){
int now = i;cir_tot++;
do{
for(auto e : G[now])
if(iscir[e.to]){
cir[cir_tot].push_back(e.to);
vis[now = e.to] = 1;break;
}
}while(now != i);
}
}
inline void solve(){
for(int id = 1;id <= cir_tot;id++){
reverse(cir[id].begin(),cir[id].end());
ll tmp = 0;
for(int x : cir[id])
dfs(x),tmp = max(tmp,ans[x]);
for(int i = 0;i < cir[id].size();i++){
int u = cir[id][i],pre = i == 0 ? cir[id][cir[id].size() - 1] : cir[id][i - 1];
for(auto e : G[u])
if(e.to == pre){sum[i] = sum[i + cir[id].size()] = e.dist;break;}
}
for(int i = 1;i < cir[id].size() * 2;i++)
sum[i] += sum[i - 1];
for(int i = 0;i < cir[id].size() * 2;i++)
val[i] = f[cir[id][i % cir[id].size()]] - sum[i];
q[head = tail = 1] = 0;
for(int i = 1;i < cir[id].size() * 2;i++){
while(head <= tail && q[head] <= (i - (int)cir[id].size()))head++;
if(head <= tail)tmp = max(tmp,f[cir[id][i % cir[id].size()]] + sum[i] + val[q[head]]);
while(head <= tail && val[q[tail]] <= val[i])tail--;
q[++tail] = i;
}
ansout += tmp;
}
}
inline void addedge(int from,int to,int dist){
G[from].push_back(Edge{to,dist});
deg[from]++;
rG[to].push_back(Edge{from,0});
}
int main(){
n = read();
for(int u,v = 1;v <= n;v++)
u = read(),deg[u]++,G[u].push_back(Edge{v,read()}),rG[v].push_back(Edge{u,0});
topo();
solve();
printf("%lld\n",ansout);
return 0;
}