板子题 Sol

RT

Cyber_Tree 出了一道板子题。。。
这题乍看之下貌似还不戳,但如果您做过类似的题,那么这就是一道板子题。。。。

首先明确要求的是什么,如果我们只考虑权值最大而不考虑最小距离,那么要求的显然就是森林中每棵基环树的最长链,即直径。。。
如果您已经知道了怎么求,那么可以直接关闭该题解了。。。。。

考虑如何求一棵基环树的直径。
发现如果直径上包含环上的边会很难求,因此可以分类讨论,分别求得不包含环上的边的最长链和包含环上的最长链即可。。。

首先考虑不包含链上的点的情况,显然可以忽略所有环上的边,然后用一个简单的树形 \(dp\) 解决。与此同时还可以求得对于环上的每一个点 \(u\) ,仅经过非环边与其距离最远的点距离是多少,记为 \(f_{u}\)

接下来考虑若包含链上的边该怎么求,显然对于环上任意两个点 \(u, v\) ,显然经过这两个的最长链长度为 \(f_{u}+f_{v}+dis_{u, v}\) ,如果直接枚举每个点对显然是 \(O(n^{2})\) 的,因此考虑优化。
环上的问题不难想到破环为链,破环为链后两个点的距离可以用前缀和表示,则原式可以被表示为 \(f_{u}+f_{v}+sum_{u}-sum_{v}\) ,这个式子显然可以用单调队列进行优化,每次将 \(f_{u}-sum_{u}\) 插入队列即可。

以上,为基环树的直径求法。。。

然而此题还包含的一个最小距离,该怎么处理呢?
直接将 \(f\) 数组改成一个二元组,重载运算符后进行相同操作即可。

如果没有做过基环树直径的题,可以拿来练手,题中有亿些细节需要注意。。。

code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define t first
#define w second
const int N=1e6+10;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, deg[N], to[N], q1[N], stk[N<<1], top;
struct node{
	ll x, y;
	friend node operator + (const node &a, const node &b) { return (node){a.x+b.x, a.y+b.y}; }
	friend node operator - (const node &a, const node &b) { return (node){a.x-b.x, a.y-b.y}; }
	friend bool operator < (const node &a, const node &b) { return a.x==b.x ? a.y > b.y : a.x < b.x; }
	friend bool operator <= (const node &a, const node &b) { return a.x==b.x ? a.y >= b.y : a.x < b.x; }
	friend bool operator == (const node &a, const node &b) { return a.x==b.x&&a.y==b.y; }
}wr[N];
node sum[N<<1];
node f[N], maxn, ans;
vector<pair<int, node> > l[N];
queue<int> q;
bool cir[N], vis[N], in[N];
inline void topsort(){
	for(int i=1; i<=n; ++i) if(deg[i]==1) q.push(i), cir[i]=1;
	while(!q.empty()){
		int u=q.front(); q.pop();
		for(pair<int, node> v : l[u])
			if(!cir[v.t]&&(--deg[v.t])==1) q.push(v.t), cir[v.t]=1;
	}
}
void pre_dfs(int u, int fa){
	vis[u]=1;
	for(pair<int, node> v : l[u]){
		if(!cir[v.t]&&v.t!=fa&&!in[v.t]&&!cir[u]) stk[++top]=v.t, sum[top]=v.w, in[v.t]=1;
		if(vis[v.t]) continue;
		pre_dfs(v.t, u);
	}
}
void dfs1(int u, int fa){
	f[u]=(node){0, 0};
	for(pair<int, node> v : l[u]){
		if(v.t==fa||!cir[v.t]) continue;
		dfs1(v.t, u);
		maxn=max(maxn, f[u]+v.w+f[v.t]);
		f[u]=max(f[u], f[v.t]+v.w);
	}
	maxn=max(maxn, f[u]);
}
signed main(void){
	freopen("naive.in", "r", stdin);
	freopen("naive.out", "w", stdout);
	n=read();
	for(int i=1; i<=n; ++i)	to[i]=read(), wr[i]=(node){read(), read()};
	for(int i=1; i<=n; ++i){
		if(to[to[i]]==i&&(wr[i]<wr[to[i]]||(wr[i]==wr[to[i]]&&to[i]<i))) continue;
		l[i].push_back(make_pair(to[i], wr[i]));
		l[to[i]].push_back(make_pair(i, wr[i]));
		++deg[to[i]]; ++deg[i];
	}
	topsort();
	for(int i=1; i<=n; ++i){
		if(vis[i]) continue;
		top=0; pre_dfs(i, 0); node tmp=(node){0, 0}; maxn=(node){0, 0};
		if(!top){
			dfs1(i, 0); tmp=maxn; maxn=(node){0, 0};
			ans=ans+tmp; continue;
		}
		for(int j=1; j<=top; ++j)
			dfs1(stk[j], 0), tmp=max(maxn, tmp), maxn=(node){0, 0};
		for(int j=1; j<=top; ++j) stk[j+top]=stk[j], sum[j+top]=sum[j];
		for(int j=1; j<=top<<1; ++j) sum[j]=sum[j]+sum[j-1];
		int l=1, r=0;
		for(int j=1; j<=top<<1; ++j){
			while(j-q1[l]>=top&&l<=r) ++l;
			if(l<=r) tmp=max(tmp, f[stk[j]]+f[stk[q1[l]]]+sum[j]-sum[q1[l]]);
			while(f[stk[q1[r]]]-sum[q1[r]]<=f[stk[j]]-sum[j]&&l<=r) --r;
			q1[++r]=j;
		}
		ans=ans+tmp;
	}
	printf("%lld  %lld\n", ans.x, ans.y);
	return 0;
}
posted @ 2021-09-04 21:45  Cyber_Tree  阅读(123)  评论(2编辑  收藏  举报