zoukankan      html  css  js  c++  java
  • 点分治及题目

    点分治

    一般可以用于处理大规模树上路径问题

    既然是处理路径问题,那么可以把路径分成两种,经过当前根节点的路径,不经过当前根节点的路径
    处理完经过当前根节点的路径,然后删掉根节点,此时肯定会形成一个或多个子树,那么剩下的不经过当前根节点的路径,递归到这些子树中处理
    删掉的节点肯定在接下来的处理中就不会被考虑了,这就意味着并不会有路径被重复统计

    然而如果图是一个链,那么一共要处理 (n) 个根节点,每次的处理一般不会低于 (O(n)),那么复杂度大于 (O(n^2)) 不够优秀
    所以在选取根节点的时候,要选取当前子树的重心,即当前子树中,最大的子树最小的一个节点
    重心的性质:以重心为根,重心的每个子树的大小,都不会超过整个树大小的一半,证明一下

    • 假设重心是 (u),它的一个子树大小超过整个树大小的一半,设这个子树的根是 (v)(与 (u) 相邻)
    • (size_i) 表示以 (i) 为根的子树大小,则 (size_v>frac{size_u}{2})
    • 那么,(u) 除了子树 (v) 以外的其它所有子树(把 (u) 本身也计算在内)的大小是 (size_u-size_v)
    • 所以,如果以 (v) 为重心,则它的一个子树是 (size_u-size_v),这个子树就是以 (u) 为根的那个。(size_u-size_v<size_v),此时,可以说明以 (u) 为根的那个子树,就是以 (v) 的所有子树中大小最大的那个,同时,也说明如果以 (v) 为重心,最大子树的大小小于以 (u) 为根最大子树的大小,则矛盾。得证

    因此,最多把整个树递归 (log n) 层即可,然后可以类比在数列上的分治,同一递归深度处理的 总复杂度(n) 有关,并且都相同,则复杂度是 (O( ext{每次处理的复杂度}log n))

    可以从这里看树的重心的其他性质的证明:https://www.cnblogs.com/suxxsfe/p/13543253.html

    模板题:P3806 【模板】点分治1
    给定一棵有 (n) 个点的树,(m) 次询问树上距离为 (k) 的点对是否存在。
    (kle 10^7,nle 10^4,mle 100)

    离线做,考虑能不能 (O(n)) 求出对于每一个询问,有没有一条符合要求的穿过当前根节点的路径
    枚举当前根的每一个子树,算出子树中每一个点距离根的距离,存入 (tmp) 数组,看一看之前是不是存在一个长度为 (k-tmp_i) 的路径
    如何判断是否存在?用一个 (judge) 数组,(judge_i) 表示目前有没有一条从某个已经访问过的子树到根节点的,长度为 (i) 的路径
    就判断 (judge_{k-tmp_i}) 就行,当 (tmp) 所有元素判断完之后,对于所有元素 judege[tmp[i]]=1
    这样就能统计到所有的路径,同时又不会出现路径”折返“的情况,就是两个在同一子树中的节点被计算(因为他们的路径不会经过当前的根)
    然后每个 (tmp) 中的距离都要存一下,用来清空 (judge),不能 memset

    找重心的过程用 find 函数实现,注意要调用两遍,原因在注释里
    复杂度 (O(nmlog n))

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<map>
    #include<iomanip>
    #include<cstring>
    #define reg register
    #define EN std::puts("")
    #define LL long long
    inline int read(){
    	register int x=0;register int y=1;
    	register char c=std::getchar();
    	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
    	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
    	return y?x:-x;
    }
    #define N 10006
    #define M 20006
    struct graph{
    	int fir[N],nex[M],to[M],w[M],tot;
    	inline void add(int u,int v,int z){
    		to[++tot]=v;w[tot]=z;
    		nex[tot]=fir[u];fir[u]=tot;
    	}
    }G;
    int n,m,sum,root;//sum 记录当前树的大小,root 记录当前找到的重心 
    int ask[106],ans[106];
    int size[N],max[N],vis[N];//vis 记录一个节点是不是已经被删除 
    int tmp[N],dis[N],que[N];
    bool judge[10000006];
    void find(int x,int fa){
    	size[x]=1;max[x]=0;//函数会多次调用,所以这里要初始化 
    	for(reg int v,i=G.fir[x];i;i=G.nex[i]){
    		v=G.to[i];
    		if(v==fa||vis[v]) continue;
    		find(v,x);
    		size[x]+=size[v];
    		if(max[x]<size[v]) max[x]=size[v];
    	}
    	if(max[x]<sum-size[x]) max[x]=n-size[x];
    	if(max[x]<max[root]) root=x;
    }
    void get_dis(int u,int fa){
    	tmp[++tmp[0]]=dis[u];
    	for(reg int v,i=G.fir[u];i;i=G.nex[i]){
    		v=G.to[i];
    		if(v==fa||vis[v]) continue;
    		dis[v]=dis[u]+G.w[i];
    		get_dis(v,u);
    	}
    }
    inline void calc(int u){
    	que[0]=0;
    	for(reg int v,i=G.fir[u];i;i=G.nex[i]){
    		v=G.to[i];
    		if(vis[v]) continue;
    		tmp[0]=0;dis[v]=G.w[i];
    		get_dis(v,u);
    		for(reg int j=1;j<=tmp[0];j++){
    			for(reg int k=1;k<=m;k++)//遍历询问
    				if(ask[k]>=tmp[j]) ans[k]|=judge[ask[k]-tmp[j]];
    		}
    		for(reg int j=1;j<=tmp[0];j++)if(tmp[j]<=1e7){
    			//更改 judge,为后面的子树的查询使用,同时加入队列 
    			judge[tmp[j]]=1;que[++que[0]]=tmp[j];
    		}
    	}
    	for(reg int i=1;i<=que[0];i++) judge[que[i]]=0;
    }
    void divide(int u){
    //		std::printf("now : %d
    ",u);
    	vis[u]=judge[0]=1;
    	calc(u);
    	for(reg int v,i=G.fir[u];i;i=G.nex[i]){
    		v=G.to[i];
    		if(vis[v]) continue;
    		root=0;sum=size[v];
    		find(v,0);find(root,0);
    		divide(root);
    	}
    }
    int main(){
    	n=read();m=read();
    	for(reg int u,v,z,i=1;i<n;i++){
    		u=read();v=read();z=read();
    		G.add(u,v,z);G.add(v,u,z);
    	}
    	for(reg int i=1;i<=m;i++) ask[i]=read();
    	max[0]=sum=n;
    	find(1,0);find(root,0);
    	//此处要调用两遍,因为在 divide 函数中,要用到 size[v]
    	//而如果不以 root 为再调用一遍,size[v] 的值就是错的(以 1 为根,而实际上应该是以 root 为根)
    	//所以 divide 里再找到的重心也就是错的 
    	divide(root);
    	for(reg int i=1;i<=m;i++) std::puts(ans[i]?"AYE":"NAY");
    	return 0;
    }
    
    

    CF161D Distance in Tree

    传送门
    输入点数为 (n) 一棵树
    求树上长度恰好为 (k) 的路径个数

    和模板几乎一样,就把 (judge) 原来的记录”有没有“,改成记录”有几条”就行了
    代码

    P4178 Tree

    here
    给定一棵 (n) 个节点的树,每条边有边权,求出树上两点距离小于等于 (k) 的点对数量。
    (nle 4cdot 10^4,kle 2cdot 10^4)

    一开始想的是对每个 (k'le k),按上个题求出长度恰好等于 (k') 的答案,但是复杂度不行
    其实要把 (judge) 数组改成线段树,把每个 (tmp_i) 作为下标单点加一下就好,然后清空的时候是单点赋值,对于区间 ([0,k-tmp_i]) 用一个区间求和加到答案里就行了
    code

    P2634 [国家集训队]聪聪可可

    here
    树上任选两点,求这两点间距离是 (3) 的倍数的概率是多少

    问题转化为求有多少对点,之间的距离是 (3) 的倍数,然后除以 (n^2)
    就是把CF那题放在 (mod 3) 意义下,注意在这里 ((u,v),(v,u)) 算两个,且 ((u,u)) 也算,那么答案要乘二再加 (n)
    code

    P2664 树上游戏

    here
    前面几个都太水了,这个稍微有点难度
    一棵树,树的每个节点有个颜色。给一个长度为 (n) 的颜色序列,定义 (s(i,j))(i)(j) 的颜色数量。以及

    [sum_i=sum_{j=1}^n s(i, j) ]

    求出所有的 (sum_i)

    用点分治以后,问题变成如何求出以根为 LCA 的点对的答案贡献
    如果有一个点的颜色,在从根到它的路径上是第一次出现,那么他(设为 (u))可以为根的 其它 子树的答案都贡献上 (size_u)
    因为其它的节点都可以有一个到他子树的路径,会经过他,而他的颜色又是第一次出现
    (all=sum size_u)
    再设 (color\_sum_i),为颜色 (i)(all) 的贡献的总值是多少,就是所有颜色在从根到它的路径上是第一次出现,并且颜色是 (i) 的节点的 (size) 之和
    我们可以通过一遍 dfs 来求出上面的信息

    然后枚举每一个子树,尝试求出这个子树中所有点的答案
    首先肯定要把这个子树对 (all,color\_sum) 的贡献都去掉,因为上面说了这是对其它子树答案的贡献
    去掉的过程稍有麻烦,具体看代码能想明白
    然后再来一遍 dfs,做出答案
    我们设 (num)(u) 到根路径上的不同的颜色数,(all')(all)(减去此子树贡献的)减掉从 (u) 到路径上每个不同颜色,的 (color\_sum)
    则对答案的贡献是 (numcdot (size_{root}-size_x)-sum color\_sum),此处 (x) 是当前子树的根
    不难理解,(sum color\_sum) 意思就是 (u) 想要到达根,就要经过这些颜色,所以再在其它子树中经过,就不能再算了,贡献不能计算进去
    而一共有 (size_{root}-size_x) 个点可以到达,没到达一个点,在从 (u) 到根的路径中,都有 (num) 个不同颜色为答案产生贡献,所以加上 (numcdot (size_{root}-size_x))
    最后做完这遍 dfs 要把刚才去掉的贡献再加回去

    然后枚举完清零,也是 dfs 实现

    根节点的答案单独计算,是 (all-color\_sum(color_{root})+size_{root}),这点挺好理解

    code

    P4149 [IOI2011]Race

    here
    给一棵树,每条边有权。求一条简单路径,权值和等于 (k),且边的数量最小。

    裸的很,就维护距离的同时维护一个 (num_i) 表示距离为 (i) 的点,最少经过几条边
    然后把 (tmp)(judge) 里存的时候,再维护一个 (numed),维护的是当前根的所有子树的 (num),然后清空 (num) 数组,更新答案
    code

  • 相关阅读:
    小白逆袭的真实故事,句句干货不看损失一个亿!
    CODEFORCES 1367F2 FLYING SORT (HARD VERSION)
    整理:iOS开发知识点
    EBS-子库存转移和物料搬运单区别
    Oracle ERP系統借贷关系表
    ORACLE ERP各模块会计分录
    SQL Queries and Multi-Org Architecture in Release 12
    ORALCE EBS ALERT 初体验
    工作流邮件审批设置
    How to Send an Email Using UTL_SMTP with Authenticated Mail Server
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/12870120.html
Copyright © 2011-2022 走看看