P8260 [CTS2022] 燃烧的呐球

P8260 [CTS2022] 燃烧的呐球

题意

已知 \(n\) 个顶点的有根树,以及 \(m\) 个二元组 \((x_i,y_i)\),其中 \(x_i,y_i\) 是树的顶点。

对于树的顶点 \(a,b\),定义 \(D(a,b)\) 为:在以 \(a\) 为根的子树中,但不在以 \(b\) 为根的子树中的顶点个数。

你需要求出以这些二元组为顶点的完全图的最小生成树,其中 \((x_i,y_i)\)\((x_j,y_j)\) 之间的边权是 \(D(x_i,x_j)+D(x_j,x_i)+D(y_i,y_j)+D(y_j,y_i)\)

\(1\le n\le 10^6,1\le m\le 10^5\)

思路

题意有点绕。简单来说就是有 \(m\) 个二元组 \((x_i,y_i)\),两个二元组的距离是 \(d_{x_i,x_j} + d_{y_i,y_j}\),其中:

\[d_{x,y}= \begin{cases} siz_x-siz_y , & \text{x 是 y 的祖先} & (1)\\ siz_y-siz_x , & \text{y 是 x 的祖先} & (2) \\ siz_x+siz_y , & \text{x,y 互不为祖先} & (3) \end{cases} \]


要求完全图最小生成树,我们考虑 Boruvka 算法。

一共进行 \(\log n\) 轮,我们考虑每一轮如何扩展边。

找每个连通块最小的出边,可以找每个点最小出边然后取 \(\min\)

如果只有 \(d_{x_i,y_i}\) 就好做,只需要考虑一维,但是它是二元组啊。

考虑变形一下,让两个二元组的距离拆开贡献。

\[\begin{cases} (siz_{x_i}+siz_{y_i})+(siz_{x_j}+siz_{y_j}), & \text{$(x_i,y_i),(x_j,y_j)$ 都是情况 3}\\ (siz_{x_i}+siz_{y_i})+(siz_{x_j}-siz_{y_j}), & \text{$(x_i,y_i)$ 是情况 3,$(x_j,y_j)$ 是情况 1}\\ \dots & \text{(我不列了)} \end{cases} \]

我们只需要讨论 \(O(1)\) 种情况,预处理的时候排序。找最小的时候需要找 \(x\) 在 dfs 序上一段区间,\(y\) 在 dfs 序上另一段区间,而且所处连通块不同。

所处连通块不同的要求,我们可以维护无限制最优解和与这个解颜色不同的最优解两个信息。然后两维的话我们树套树,树链剖分。第一层树剖两只 \(\log\),第二层树剖也是两只 \(\log\),总的复杂度是 \(5 \log\),常数小,但是别说空间,时间就显然过不了。


以下“祖先”“后代”均指 \(j\)\(i\) 的祖先/后代。

我们用树剖是因为我们找的 \(j\) 可能有一维是祖先关系,有一维是后代关系。我们分类讨论一下:当搜到 \(i\) 时,

  • \(i\) 两维都是 \(j\) 的祖先:可以在树上枚举第一维,然后第二维只有后代关系,按照第二维的时间戳做线段树合并。
  • \(j\) 两维都是 \(i\) 的祖先:在树上枚举第一维,按照第二维存到全局平衡二叉树里,然后每次在全局平衡二叉树上查询 \(i\) 到根的链。可以开一个桶记录全局平衡二叉树的操作以便撤销。
  • 一维祖先一维后代:在树上枚举祖先那一维,按照另一维的时间戳存到线段树里,查询就是线段树区间查询。

每种情况都是一只 \(\log\)。对于无关的维,因为无关的情况下 \(d\) 的符号都是 \(+\),所以我们可以按照无关算 \(i\) 与任意 \(j\) 的距离,算出来的答案不会正确答案更小。即:

  • 一维祖先一维无关:在树上枚举祖先那一维,维护最优答案。
  • 一维后代一维无关:在树上枚举后代那一维,维护每个子树的最优答案。
  • 两维均无关:所有点对找出两个最优的答案,一定有一个是合法的。

这几个都是没有 \(\log\) 的。

因此总时间复杂度 \(O(n \log^2)\)

code

不会全局平衡二叉树,代码先鸽掉。

看起来不好写呢。不想写。我写的话,不过度压行,而且还不简洁,还要调试,不得 \(200\) 来行了?

唉,写吧写吧。

不想写了。继续鸽掉。

写了一点的代码如下:

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
	constexpr int N=1e6+7,M=1e5+7,inf=0x3f3f3f3f;
	int n,m;
	int fa[N];
	vector<int> son[N];
	int x,y;
	struct node {
		int x,y;
	}a[M];
	int to[M];
	int find(int u) { return to[u]==u ? u : to[u]=find(to[u]); }
	int cnt;
	struct mindis {
		int v,d;
		void _min(mindis b,int c=0) { if(b.d+c<d) v=b.v,d=b.d+c; }
	}dis[M];//点对最近的点对编号及距离
	int siz[N];
	int dfn[N],dfn0,ed[N];
	void dfs(int u) {
		dfn[u]=++dfn0;
		siz[u]=1;
		for(int v : son[u]) {
			dfs(v);
			siz[u]+=siz[v];
		}
		ed[u]=dfn0;
	}
	vector<int> _vec[N],__vec[N];
	auto vec=&_vec;
	void init() {
		rep(i,1,m) _vec[a[i].x].push_back(i), __vec[a[i].y].push_back(i);
		dfs(1);
	}
	struct dseg {//动态开点线段树
		void clear() {

		}
		mindis query(int l,int r,int c,int u=1) {

		}
		void insert(int x,int val,int c,int u=1) {

		}
	}*tr1[N];
	void merge(dseg *x,dseg *y) {//线段树合并

	}
	void solve1(int u) {//两维都在子树里找
		tr1[u]->clear();
		for(int v : son[u]) {
			solve1(v);
			merge(tr1[u],tr1[v]);
		}
		for(int id : (*vec)[u]) dis[id]._min(tr1[u]->query(dfn[a[id].y],ed[a[id].y],find(id)),siz[a[id].x]+siz[a[id].y]); 
		for(int id : (*vec)[u]) tr1[u]->insert(dfn[a[id].y],-siz[a[id].x]-siz[a[id].y],find(id));
	}
	#define fi first
	#define se second
	typedef pair<int,mindis> pim;
	struct qjphecs {//全局平衡二叉树
		mindis mn1[N],mn2[N];
		int top_mn1,top_mn2;
		vector<pim> vec_mn1,vec_mn2;
		void clear() {

		}
		void add(int u,int w,int c) {

		}
		mindis query(int u,int c) {

		}
		void ctrl_z(int la_mn1,int la_mn2) {

		}
	}tr2;
	void solve2(int u) {//两维都在祖先里找
		int la_mn1=tr2.top_mn1,la_mn2=tr2.top_mn2;
		for(int id : (*vec)[u]) dis[id]._min(tr2.query(a[id].y,find(id)),-siz[a[id].x]-siz[a[id].y]);
		for(int id : (*vec)[u]) tr2.add(a[id].y,siz[a[id].x]+siz[a[id].y],find(id));
		for(int v : son[u]) solve2(v);
		tr2.ctrl_z(la_mn1,la_mn2);
	}
	struct seg {//线段树
		
		vector<pim> vec_mn1,vec_mn2;
		void clear() {

		}
		mindis query(int l,int r,int c,int u=1) {

		}
		void insert(int x,int val,int c,int u=1) {

		}
	}tr3;
	void solve3(int u) {//一维祖先一维后代
		for(int id : (*vec)[u]) dis[u]._min(tr3.query(dfn[a[id].y],ed[a[id].y],find(id)),-siz[a[id].x]+siz[a[id].y]);
		for(int id : (*vec)[u]) tr3.insert(dfn[a[id].y],siz[a[id].x]-siz[a[id].y],find(id));
		for(int v : son[u]) solve3(v);
	}
	void solve() {
		rep(i,1,m) dis[i]={0,inf};
		vec=&_vec;
		solve1(1);
		tr2.clear();
		solve2(1);
		tr3.clear();
		solve3(1);
	}
    void main() {
		sf("%d%d",&n,&m);
		rep(i,2,n) sf("%d",&fa[i]), son[fa[i]].push_back(i);
		rep(i,1,m) sf("%d%d",&a[i].x,&a[i].y);
		rep(i,1,m) col[i]=i;
		cnt=m;
		init();
		while(cnt>1) solve();
    }
}
int main() {
    #ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("my.out","w",stdout);
    #endif
    wing_heart :: main();
}
posted @ 2025-02-05 11:08  wing_heart  阅读(27)  评论(0)    收藏  举报