ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[交叉坐标的星尘]题解

2021-06-30 18:03:59  阅读:201  来源: 互联网

标签:结点 星尘 int 题解 染色 树链 坐标 lca else


题目链接

交叉坐标的星尘

题意简述

题目中说的比较清楚 , 这里再粘贴一遍 :

给定一棵 \(n\) 个点的树 . 你需要从 \(1\) 号点开始 , 不断地给树上的结点染色 . 在某一轮染色过程中 , 假设你是从编号为 \(u\) 的结点开始染色 , 现在染色到编号为 \(v\) 的结点 , 那么下面你只能尝试给编号为 \(v+1\) 的结点染色 . 如果存在一条树链 , 使得 \(u,u+1,\dots,v,v+1\) 都在这条树链上 , 那么编号为 \(v+1\) 的结点被成功染色 ; 否则 , \(v+1\) 在这一轮中不被染色 , 你只能开启新一轮的染色过程 , 并以 \(v+1\) 作为新一轮染色过程的起始结点 .

设直到结点 \(n\) 被染色 , 一共经历了 \(m\) 轮染色 . 你需要输出染色轮数 \(m\) .

题目分析

本题考察知识 :

对于前 \(30\%\) 的数据 , 考察树的基本知识 , 以及对树的 \(\rm dfs\) 算法 , 属于入门组难度 .

对于前 \(60\%\) 的数据 , 考察对树链性质的观察 , 属于普及组难度 .

对于 \(100\%\) 的数据 , 考察对树链性质的剖析 , 最近公共祖先和树上 \(\rm dfs\) 序的掌握 , 属于提高组难度 .

各档部分分解法

\((1):n=200\)

读完题目 , 你会发现按题意模拟就可以了 . 从 \(1\sim n\) 枚举每一个结点 , 假设当前枚举到 \(v\) (尝试对 \(v\) 染色) , 而这一轮染色的起始结点为 \(u\) . 那么我们尝试找到一条树链能够覆盖 \(u,u+1,\dots, v-1,v\) . 那么 , 你可以尝试 \(\mathcal O(n^2)\) 地枚举所有树链 , 然后判断它们是否都在树链上 , 只要存在一条即可 .

对于枚举树链 , 有一个较简单的做法 , 就是枚举 \(u=1\sim n\) 分别为根节点 , 然后做 \(n\) 遍 \(\rm dfs\) 即可 .

这样的复杂度是 \(\mathcal O(n^3)\) 的 . 当然还有针对该算法的优化 , 在某一轮染色中 , 起始结点是 \(u\) , 你可以二分 \(v=u+1\sim n\) , 来判断 \(u\sim v\) 是否被一条树链覆盖 . 这样的复杂度是 \(\mathcal O(mn^2\log n)\) 的 , 在 \(m\) 比较小的时候要比上面 \(\mathcal O(n^3)\) 的算法优秀 , 但是很可惜 , 出题人并没有特意给这种做法相应的部分分 . (因为 ... 太懒了)

\((2):n=2000\)

注意到 , 对于 \(u,u+1,\dots , v-1,v\) , 可能会有多条树链将其覆盖 . 但是一定会存在这样的树链 , 它的两端 \((x,y)\) 满足 \(u\leq x,y\leq v\) , 也就是 \(x,y\) 是 \(u\sim v\) 内的结点 . 并且这样的树链是唯一的 , 最短的 . 可以自己画画图 , 进行理解 . 为了方便叙述 , 我们称这样的链是覆盖链 .

假设初始染色结点为 \(u\) , 当前枚举到 \(v\) (尝试对 \(v\) 染色) , \(u\sim v-1\) 的覆盖链为 \((x,y)\) . 那么 , 如果存在新的对 \(u\sim v\) 的覆盖链 , 其中一个端点一定为 \(x\) 或者 \(y\) 或者 \(v\) , 分别枚举 , 判断并更新新的覆盖链即可 . 这样 , 一次判断就被优化到了 \(\mathcal O(n)\) . 总复杂度为 \(\mathcal O(n^2)\) , 可以通过该部分分 .

\((3):n=200000\)

前排提示 , 出题人造了菊花树(树的平均度数很大) , 链图(树的深度很大) , 蒲公英(树的平均度数和深度都较大) 的数据 , 应该能把暴力卡掉 (希望吧 QAQ) , 如果你使用关于深度和度数的暴力程序拿到了满分 , 那说明 , 您太强了 !

前置知识为最近公共祖先和树上 \(\rm dfs\) 序 , 不会的同学可以直接问 HDM 什么时候教 , 或者找找蓝书(算法竞赛进阶指南) , 或者查看 oi-wiki 上的相关知识 .

\(\rm dfs\) 序在本题的应用为 , 判断两个结点 \((u,v)\) , \(v\) 是否在 \(u\) 的子树内 .

我们以任意一个结点为根节点 , 做好求 lca 的预处理和 dfs 序的预处理 ; 对于覆盖链 , 我们维护其两个端点和两个端点的 lca : \((u,v,lca)\) .

覆盖链有三种情况 : \((1):u=v\) , 覆盖链是一个点 ; \((2):u\) 是 \(v\) 的祖先 , 即从 \(v\) 一直往父亲走可以走到另一个端点 \(u\) ; \((3):u\) 和 \(v\) 的最近公共祖先 \(lca\neq u,\neq v\) , 也就是从 \(v\) 沿着父亲走到 \(lca\) , 再从 \(lca\) 向儿子走才能走到 \(u\) . 我们分别维护这三种情况的覆盖链 , 并且在 \((2)\) 情况时 , 为了方便 , 我们令 \(u\) 是 \(u,v\) 中深度更小的那个结点 .

当前覆盖链是 \((1)\) 情况时 :

考虑新枚举的结点 \(x\) . 显然新的覆盖链的两个端点为 \(u\) 和 \(x\) . 只需要考虑 \(u\) 和 \(x\) 的位置关系即可 .

当前覆盖链是 \((2)\) 情况时 :

如果 \(x\) 在 \(u\) 的子树之外 , 那么新的覆盖链显然为 \((v,x,\mathrm{lca}(v,x))\) . 如果 \(x\) 在 \(u\) 的子树之内 , 有如下情况 :

\(1.\ x\) 在 \(v\) 的子树之内 , 可以形成新的覆盖链 ;

\(2.\ x\) 不在 \(v\) 的子树之内 , 但是 \(v\) 在 \(x\) 的子树之内 , 说明 \(x\) 在 \(u\rightarrow v\) 这一条树链上 ;

\(3.\ x\) 不在 \(v\) 的子树之内 , 但是 \(\mathrm{lca}(v,x)=u\) , 说明可以形成 \(v\rightarrow u\rightarrow x\) 的新覆盖链 ;

对于其他的情况 , 不能形成新的覆盖链了 .

当前覆盖链是 \((3)\) 情况时 :

这部分就比较简单了 , 尝试自己想想 ? 不会的话 , 可以看标程是如何实现的 .

参考程序

题解使用的是树链剖分求 \(\rm lca\) , 这可能会促使你去看一看树链剖分的内容 (提示 , 提高组可能不考) . 最一般的方法是倍增求 \(\rm lca\) .

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
 
const int N=202020,M=404040;
int n,m;
int val[N];
int head[N],ver[M],nxt[M],num,tot=1;
int dfn[N],In[N],Out[N],cnt,fa[N],son[N],dep[N],top[N],sz[N];

void add(int x,int y) {
	ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
}
void dfs1(int x,int father) {
	fa[x]=father,sz[x]=1;
	for(int i=head[x];i;i=nxt[i]) {
		int y=ver[i];
		if(y == father)	continue;
		dep[y]=dep[x]+1,dfs1(y,x),sz[x]+=sz[y];
		if(sz[y] >= sz[son[x]])
			son[x]=y;
	}
}
void dfs2(int x,int apex) {
	top[x]=apex,dfn[x]=++cnt,In[x]=cnt;
	if(son[x])	dfs2(son[x],apex);
	for(int i=head[x];i;i=nxt[i]) {
		int y=ver[i];
		if(y == fa[x] or y == son[x])	continue;
		dfs2(y,y);
	}
	Out[x]=cnt;
}
int Lca(int x,int y) {
	while(top[x] != top[y]) dep[top[x]] < dep[top[y]] ? (swap(x,y) , x=fa[top[x]]) : x=fa[top[x]] ;
	return dep[x] < dep[y] ? x : y;
}
bool Insubtree(int root,int x) {return In[root] <= dfn[x] and dfn[x] <= Out[root];}//Is x in subtree(root) ?
void solve() {
	int u=1,v=1,lca=1;
	bool ischain=1;
	m=1; 
	for(int i=2;i<=n;++i) {
		int x=i;
		if(u == v) {
			if(Insubtree(u,x))
				v=x,lca=u;
			else if(Insubtree(x,u))
				u=x,lca=x;
			else 
				lca=Lca(u,x),v=x,ischain=0;	
		}
		else if(ischain == 1) {
			if(Insubtree(u,x)) { // x in subtree(u) , u -> x
				if(Insubtree(x,v)) // v in subtree(x) , u -> x -> v
					;
				else if(Insubtree(v,x)) // x in subtree(v) , u -> v -> x
					v=x;
				else if(Lca(v,x) == u)	// x in the other subtree of u , x -> u -> v
					u=x,ischain=0;
				else //over !
					++m,u=v=lca=x,ischain=1;
			}
			else if(Insubtree(x,u))// u in subtree(x), x -> u -> v
				u=x,lca=x;
			else {
				// x if not only not the ancestor of u but also not the subpoint of u
				lca=Lca(u,x),u=x,ischain=0;
			}
		}
		else {
			if(Insubtree(lca,x)) { // x in subtree(lca) , lca -> x
				if(Insubtree(x,v)) // v in subtree(x) , lca -> x -> v
					;
				else if(Insubtree(x,u)) // u in subtree(x) , lca -> x -> u
					;			
				else if(Insubtree(v,x)) // x in subtree(v) , u -> lca -> v -> x
					v=x;
				else if(Insubtree(u,x)) // x in subtree(u) , v -> lca -> u -> x
					u=x;
				else //over !
					++m,u=v=lca=x,ischain=1;

			}					
			else // over !
				++m,u=v=lca=x,ischain=1;

		}
	}	
}
int main() {
	scanf("%d",&n);
	for(int i=1,u,v;i<n;++i) {
		scanf("%d %d",&u,&v);
		add(u,v),add(v,u);
	}
	dfs1(1,0);
	dfs2(1,1);
	solve();
	printf("%d\n",m);
	return 0;
}

标签:结点,星尘,int,题解,染色,树链,坐标,lca,else
来源: https://www.cnblogs.com/zyc2003/p/14955734.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有