• [提高组集训2021] 超级加倍


    一、题目

    我们认为 (x ightarrow y) 的简单路径是好的,当且仅当路径上的点最小的是 (x),最大的是 (y)

    给出一棵 (n) 个点的树,求出好的简单路径条数。

    (nleq 2cdot 10^6)

    二、解法

    很容易写出暴力点分治,但是因为需要解决二维偏序问题所以是 (O(nlog^2n)) 的。

    首先考虑树是一条链的情况,发现可以二分求出 (i) 的范围,然后在这个范围中看有多少 (j) 即可。

    那么搬到树上我们需要解决求出范围这个问题,既然它又是路径最值问题我们可以使用重构树,我们从大到小加入节点,对于新加的节点 (u),我们看所有边 ((u,v)) 并且 (v) 已经被加入了,那么我们合并 (u)(v) 连通块的根,并且把 (v) 并查集的根设置成 (u),可以用并查集简单维护。

    那么重构树上两个点的 ( t lca) 就是它们真实路径上的最小值,用类似的方法可以求出第二棵树使得 ( t lca) 就是它们真实路径上的最大值。那么条件转化成第一棵树上 (x)(y) 的祖先,第二棵树上 (y)(x) 的祖先,我们可以求出第一棵树上的 ( t dfn) 序,在第二棵树上 ( t dfs),用树状数组来统计答案,时间复杂度 (O(nlog n))

    三、总结

    对重构树的理解不仅仅是最小生成树上的边重构,还可以是本题的点重构。总之就是解决一个范围的问题,也就是满足某一条件的点在树上有特定的范围(比如子树)

    //I just gonna burn the stars...
    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 2000005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,tot,t1,t2,rt[2],a[M],b[M],f[M],f1[M],f2[M];
    int fa[M],vis[M],in[M],out[M];long long ans;
    struct edge{int v,next;}e[M<<1],e1[M],e2[M];
    int find(int x)
    {
    	if(x!=fa[x]) fa[x]=find(fa[x]);
    	return fa[x];
    }
    void build(int F)
    {
    	for(int i=1;i<=n;i++) fa[i]=i,vis[i]=0;
    	for(int i=1;i<=n;i++)
    	{
    		int u=a[i];vis[u]=1;
    		for(int j=f[u];j;j=e[j].next)
    		{
    			int v=e[j].v;
    			if(vis[v])
    			{
    				if(F==0) e1[++t1]=edge{find(v),f1[u]},f1[u]=t1;
    				else e2[++t2]=edge{find(v),f2[u]},f2[u]=t2;
    				fa[find(v)]=u;
    			}
    		}
    	}
    	rt[F]=find(1);
    }
    void dfs1(int u)
    {
    	in[u]=++m;
    	for(int i=f1[u];i;i=e1[i].next) dfs1(e1[i].v);
    	out[u]=m;
    }
    int lowbit(int x)
    {
    	return x&(-x);
    }
    void add(int x,int c)
    {
    	for(int i=x;i<=n;i+=lowbit(i))
    		b[i]+=c;
    }
    int ask(int x)
    {
    	int r=0;
    	for(int i=x;i>0;i-=lowbit(i))
    		r+=b[i];
    	return r;
    }
    void dfs2(int u)
    {
    	add(in[u],1);
    	ans+=ask(out[u])-ask(in[u]);
    	for(int i=f2[u];i;i=e2[i].next) dfs2(e2[i].v);
    	add(in[u],-1);
    }
    signed main()
    {
    	freopen("charity.in","r",stdin);
    	freopen("charity.out","w",stdout);
    	n=read();
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=i;
    		int j=read();if(!j) continue;
    		e[++tot]=edge{i,f[j]},f[j]=tot;
    		e[++tot]=edge{j,f[i]},f[i]=tot;
    	}
    	build(0);reverse(a+1,a+1+n);
    	build(1);dfs1(rt[0]);dfs2(rt[1]);
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    mongodb一个关键字对多个字段同时查询,mongodb $or $and查询
    用Supervisord管理Python进程
    ImportError No module named memcache
    eclipse项目显示标尺
    Eclipse 安装插件后不显示的解决办法
    Flutter入门到放弃笔记(一)--Mac电脑配置Flutter环境
    iOS 动态库、静态库相关
    iOS 获取手机后台音频状态以及类别
    Swift 懒加载
    Swift中@ojbc 重载用法
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15535773.html
Copyright © 2020-2023  润新知