【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
",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;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ1791.html