【BZOJ1791】[IOI2008] 岛屿(水题)
大致题意: 求基环树森林中所有基环树直径之和。
前言
这就是一道大水题。。。
我也不知道为什么一开始写了两个大\(Bug\)结果一交还有\(75\)分。。。
看来这题不光题很水,数据也很水。
环上的答案
显然,我们可以分类讨论,把基环树的路径分成经过环的路径和不在环上的路径。
考虑如何求出环上的答案。
我们可以求出环上每个点子树内最长链的长度\(Mx_i\),以及环上前\(i\)条边长度的前缀和\(sum_i\)。
然后我们枚举一个\(i\)作为经过环的路径的一个关键点(即由环到树的转折点),则我们只需考虑小于\(i\)的点作为另一个关键点时的答案。(因为大于\(i\)的点作为关键点的情况会在枚到那个点的时候讨论)
对于两个关键点\(i,j\),我们有答案为:
\[max(sum_{i-1}-sum_{j-1},sum_n-sum_{i-1}+sum_{j-1})+Mx_i+Mx_j
\]
考虑最终答案也是取最大值的,因此我们完全可以把这个式子中的\(max\)拆成两项分别算答案:
\[(sum_{i-1}+Mx_i)+(-sum_{j-1}+Mx_j)
\]
\[(-sum_{i-1}+Mx_i)+(sum_n+sum_{j-1}+Mx_j)
\]
这里我已经把和\(i\)有关的项以及与\(i\)无关的项分开了,不难发现这道题还是很良心,没有同时与\(i,j\)有关的项(不然就要斜率优化,说起来也好久没写过这东西了)。
则我们直接在枚举\(i\)的同时维护好\(-sum_{j-1}+Mx_j\)和\(sum_n+sum_{j-1}+Mx_j\)最大值即可迅速求出答案。
不在环上的答案
不在环上,那么只能在某一子树中,于是问题就变成了树的直径。。。
然后考虑反正我们都已经写了一个子树最长链了,干脆就用\(DP\)求树的直径了。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define LL long long
#define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v)
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,ee,lnk[N+5],vis[N+5];struct edge {int to,nxt,val;}e[2*N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
class CircleTreeSolver
{
private:
int cnt,s[N+5],p[N+5],dep[N+5],fa[N+5],used[N+5];
LL ans,cur,sum[N+5],Mx[N+5],f[N+5],g[N+5],k[N+5];
I bool Find(CI x)//找环
{
for(RI i=lnk[x],u,v,t=0;i;i=e[i].nxt)
{
if(e[i].to==fa[x]&&!t++) continue;//注意可能有重边
if(!dep[v=e[i].to]) {if(dep[v]=dep[fa[v]=x]+1,Find(v)) return 1;continue;}
dep[u=x]<dep[v]&&swap(u,v);W(dep[u]^dep[v]) p[s[++cnt]=u]=1,u=fa[u];//先跳到同样深度
RI tmp=v,tot=0;W(p[s[++cnt]=u]=1,u^v) u=fa[u],v=fa[v],++tot;//往上跳,存下一边的点
v=tmp;W(tot) p[s[cnt+tot]=v]=1,v=fa[v],--tot;return 1;//再次上跳,存下另一边的点
}return 0;
}
I LL dfs1(CI x)//树形DP求树的直径,第一遍dfs(顺带求出子树最长链)
{
LL t;vis[x]=1;for(RI i=lnk[x];i;i=e[i].nxt) !p[e[i].to]&&!vis[e[i].to]&&
(t=dfs1(e[i].to)+e[i].val,f[x]<t?(g[x]=f[x],f[x]=t,k[x]=e[i].to):Gmax(g[x],t));
return f[x];
}
I void dfs2(CI x,Con LL& v=0,CI lst=0)//树形DP求树的直径,第二遍dfs
{
Gmax(cur,f[x]+max(g[x],v));for(RI i=lnk[x];i;i=e[i].nxt) !p[e[i].to]&&
e[i].to^lst&&(dfs2(e[i].to,max(e[i].to^k[x]?f[x]:g[x],v)+e[i].val,x),0);
}
public:
I void Solve(CI x)
{
RI i,j;for(cnt=cur=0,dep[x]=1,Find(x),s[cnt+1]=s[1],i=1;i<=cnt;++i)//枚举环上点
{
for(j=lnk[s[i]];(e[j].to^s[i+1])||used[j+1>>1];j=e[j].nxt);//找下一条边
sum[i]=sum[i-1]+e[j].val,used[j+1>>1]=1,Mx[i]=dfs1(s[i]),dfs2(s[i]);//计算边的前缀和,记下子树内最长链长度
}
LL t1=0,t2=0;for(i=1;i<=cnt;++i)//统计环上答案
Gmax(cur,t1+sum[i-1]+Mx[i]),Gmax(cur,t2-sum[i-1]+Mx[i]),//更新答案
Gmax(t1,-sum[i-1]+Mx[i]),Gmax(t2,sum[cnt]+sum[i-1]+Mx[i]);//维护辅助信息
ans+=cur;//统计最终答案(因为是森林)
}
I void Print() {printf("%lld\n",ans);}
}S;
int main()
{
RI i,x,y;for(F.read(n),i=1;i<=n;++i) F.read(x),F.read(y),add(i,x,y),add(x,i,y);
for(i=1;i<=n;++i) if(!vis[i]) S.Solve(i);return S.Print(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒