gmoj 6807. 【2020.10.29提高组模拟】tree

题目

https://gmoj.net/senior/#main/show/6807

题解

转化题意,可以发现,这道题就是选择一个根,使得它的某个子树内包含所有颜色,求满足条件的子树的最大深度。

比赛时我的思路是删掉以某个儿子为根的子树(或以当前点为根的子树外的部分),结果发现这样子处理不了删掉以孙子为根的子树的情况。最终挂在这道题上了。

其实应该考虑另外的处理方式:删掉某棵子树或删掉某棵子树外的全部点。这里的删掉指的是不选择这些点,若答案不等于1,根就在这些点中。

分别考虑这两种情况:

  1. 当我删掉一个子树时,说明这个子树外包含所有的颜色。但是这个条件比较难判断,于是考虑这个子树不能被删去时满足什么条件,显然是这个子树外缺少某种颜色,即这种颜色全都在这个子树内。对于每一种颜色,把它们的lca求出来,lca到根的路径上的点就是不能删掉子树的点,用倍增lca O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)地处理即可(但是这样子常数巨大,优化后面会讲);
  2. 当我删掉一个子树外的所有点时,说明这个子树内包含所有点。发现树上处理起来很麻烦(可以线段树合并,但是空间和时间都可能爆掉),就把它转化到序列上(按dfn序排序)。双指针 O ( n ) O(n) O(n)地扫描一下就行了(当然你喜欢的话也可以用主席树,但是可能会炸空间)。

理论上这样打就能过了,但是我常数太大TLE了……

发现跑得最慢的部分是求一堆点的lca那里,要不开#pragma GCC optimize("O3")过这题势必要优化这个部分。

这里有一个定理: ∀ d f n a ≤ d f n b ≤ d f n c , 都 有 l c a ( a , c ) = l c a ( a , b , c ) \forall dfn_a\le dfn_b\le dfn_c,都有 lca(a,c)=lca(a,b,c) dfnadfnbdfnc,lca(a,c)=lca(a,b,c)
证明的话就是 l c a ( a , c ) lca(a,c) lca(a,c)的子树中必定包含了dfn在 [ a , c ] [a,c] [a,c]中所有点,因此必定也是b的祖先。

有了这个定理就可以将这个部分优化到 O ( m log ⁡ 2 n ) O(m\log_2n) O(mlog2n)了,足以通过这道题。如果常数太大还是过不了,可以用tarjan lca

CODE

倍增lca版本,常数稍大:

#include<cstdio>
using namespace std;
#define M 2000005
#define N 1000005
#define C 100005
struct array{
   int fir[C],nex[N];}a;bool cover[N];
int fir[N],to[M],nex[M],col[N],las[C],b[C],right[N];
int f[N][20],g[N][2],son[N],h[N],dep[N],dfn[N],id[N],siz[N],cnt,s,m;
inline char gc()
{
   
	static char buf[100005],*l=buf,*r=buf;
	return l==r&&(r=(l=buf)+fread(buf,1,100005,stdin),l==r)?EOF:*l++;
}
inline void read(int &x)
{
   
	char ch;while(ch=gc(),ch<'0'||ch>'9');x=ch-48;
	while(ch=gc(),ch>='0'&&ch<='9') x=x*10+ch-48
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值