[bzoj1468][poj1741]Tree_点分治

Tree bzoj-1468 poj-1741

    题目大意:给你一颗n个点的树,求树上所有路径边权和不大于m的路径条数。

    注释:$1\le n\le 4\cdot 10^4$,$1\le m \le 10^9$。

      想法:GXZlegend给高一将点分治,去听了之后的第一道模板题。

        我们对于一类树上统计问题,除了强大的树形dp之外,我们还有分治。今天听的是点分治:

        就是说,我们将所有的链关于一个点分划成两类:过这个点的链,和不过这个点的链。这个点就是根节点,我在任意两颗子树中拎出两个点,他们之间的链,就是经过根节点的链。紧接着,我们递归处理这个过程。对于每一个子树,钦定一个根节点,然后求这个子树中经过子树的钦定节点且满足条件的链。那么,这个钦定节点如何选取?显然,树链统计问题可以O(n*n)枚举所有链,那么,我要使得这样的钦定节点可以降低枚举复杂度。于是,我在递归时要尽量使得所有的子树尽量差不多,这样时间复杂度会降下来。故,我们可以钦定树的重心,这样每次递归都求重心,时间复杂度为O(n*logn)。每一次递归的时候重新更新所有节点的所有信息,把当过重心的节点通过mark的方式删掉,就完成了点分治的过程。

        剩下的,就是一些代码:

      找重心的时候顺便更新当前子树的size

void getroot(int pos,int fa)
//与其函数名叫做getroot,倒不如数get_original,因为每一次递归都必须求重心和节点size
{
	f[pos]=0;
	size[pos]=1;
	for(int i=head[pos];i;i=nxt[i])
	{
		if(to[i]==fa||vis[to[i]]) continue;
		getroot(to[i],pos);
		size[pos]+=size[to[i]];
		f[pos]=max(f[pos],size[to[i]]);
	}
	f[pos]=max(f[pos],sn-size[pos]);
	if(f[root]>f[pos]) root=pos;
}

      回归本题,我们期望寻找到所有的链,那么我就可以在钦定完节点之后,从所有子树的所有节点中拎出所有节点的deep,我只需要找到这些deep之间和小于等于m的(先不考虑同一颗子树中的情况)。找出这些deep之后,用双指针即可求出当前链假装过重心(同一颗子树有算重的情况)的个数,然后运用容斥原理,减掉每一颗子树中的情况即可。

      由于双指针的时候需要排序,所以总的之间复杂度是$O(n\cdot log^2n)$

    最后,附上丑陋的代码... ...

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 40010
using namespace std;
int to[N<<1],head[N],cnt,nxt[N<<1],val[N<<1];
int f[N],root,m,deep[N],size[N],sn,d[N],tot,ans;
bool vis[N];
inline void add(int x,int y,int z)
{//用cnt是因为后面顺手写了tot
	to[++cnt]=y;
	val[cnt]=z;
	nxt[cnt]=head[x];
	head[x]=cnt;
}
void getroot(int pos,int fa)
//与其函数名叫做getroot,倒不如数get_original,因为每一次递归都必须求重心和节点size
{
	f[pos]=0;
	size[pos]=1;
	for(int i=head[pos];i;i=nxt[i])
	{
		if(to[i]==fa||vis[to[i]]) continue;
		getroot(to[i],pos);
		size[pos]+=size[to[i]];
		f[pos]=max(f[pos],size[to[i]]);
	}
	f[pos]=max(f[pos],sn-size[pos]);
	if(f[root]>f[pos]) root=pos;
}
void getdeep(int pos,int fa)//deep是当前节点到重心的路径边权和
{
	d[++tot]=deep[pos];//之后需要双指针
	for(int i=head[pos];i;i=nxt[i])
	{
		if(to[i]==fa||vis[to[i]]) continue;
		deep[to[i]]=deep[pos]+val[i],getdeep(to[i],pos);
	}
}
int calc(int pos)//双指针求过pos的满足条件数
{
	tot=0;
	getdeep(pos,0);
	sort(d+1,d+tot+1);
	int i=1,j=tot,sum=0;
	while(i<j)
	{
		if(d[i]+d[j]<=m) sum+=j-i,i++;
		else j--;
	}
	return sum;
}
void dfs(int pos)
{
	deep[pos]=0;
	vis[pos]=1;
	ans+=calc(pos);
	for(int i=head[pos];i;i=nxt[i])
	{
		if(!vis[to[i]])
		{
			deep[to[i]]=val[i];
			ans-=calc(to[i]);//单步容斥
			sn=size[to[i]];//求重心时用得到
			root=0;//当前子树重心root
			getroot(to[i],0);
			//现在root是重心了
			dfs(root);
		}
	}
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);add(y,x,z);
	}
	cnt=0,ans=0;
	scanf("%d",&m);
	f[0]=0x7f7f7f7f;//绝对不能让0是root的神奇操作qwq
	sn=n;
	root=0,getroot(1,0),dfs(root);
	printf("%d\n",ans);
	return 0;
}

    小结:错误在于对点分治理解不够深刻(其实是双指针的时候j++导致全盘爆炸).

      点分治是处理树上统计问题的好方法。鸣谢GXZlegend

原文地址:https://www.cnblogs.com/ShuraK/p/8782663.html

时间: 2024-08-24 18:14:11

[bzoj1468][poj1741]Tree_点分治的相关文章

[bzoj1468][poj1741]Tree[点分治]

可以说是点分治第一题,之前那道的点分治只是模模糊糊,做完这道题感觉清楚了很多,点分治可以理解为每次树的重心(这样会把数分为若干棵子树,子树大小为log级别),然后统计包含重心的整个子树的值减去各个子树的值,这样算出的就是与这个重心有关的情况的答案,比如这道题,求路径,那么就考虑在重心所在的子树中所有的路径减去不过重心的路径就是过重心的路径了.之前重心没找对...poj时间卡的紧就T了.. 1 #include <iostream> 2 #include <algorithm> 3

POJ1741:Tree——点分治

题面 POJ1741 解析  刚学了点分治,练一练模版题 过程就不多说了,主要说说细节 在每次查询下一棵子树时, 传进去的整棵子树大小是上一次的$siz$, 这个数据其实是错的, 但好像并不影响时间复杂度, 这样的话找重心就必须找最大子树最小的点了,否则会错.因此需要存一个当前最大子树最小的点的最大子树的大小, 以及当前的重心, 每次找重心之前,前者要赋为$inf$ 在每次统计以当前点为$lca$的链的答案时, 要先把这个重心加入数组中, 再按$dis$排序, 然后用双指针法或是其他可行的方法统

poj1741 Tree 点分治

入门题,算是对树分治有了初步的理解吧. #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<vector> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using nam

【BZOJ-1468】Tree 树分治

1468: Tree Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1025  Solved: 534[Submit][Status][Discuss] Description 给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K Input N(n<=40000) 接下来n-1行边描述管道,按照题目中写的输入 接下来是k Output 一行,有多少对点之间的距离小于等于k Sample Input 7 1 6 13 6

[poj1741]Tree(点分治+容斥原理)

题意:求树中点对距离<=k的无序点对个数. 解题关键:树上点分治,这个分治并没有传统分治的合并过程,只是分成各个小问题,并将各个小问题的答案相加即可,也就是每层的复杂度并不在合并的过程,是在每层的处理过程. 此题维护的是树上路径,考虑点分治. 点分治的模板题,首先设点x到当前子树跟root的距离为,则满足${d_x} + {d_y} \le k$可以加进答案,但是注意如果x,y在同一棵子树中,就要删去对答案的贡献,因为x,y会在其所在的子树中在计算一次.同一棵子树中不必考虑是否在其同一棵子树中的

POJ1741 经典树分治

题意:有一棵树,每条边有一个距离,求dis(u,v)<=k的点的对数 题解:树分治,对于一颗树上的两点,要么在同一颗子树上,要么在不同子树上,要么一个点是根,另一个在某一子树上,对于第一种情况我们可以通过递归来变成第二种或者第三种情况.我们对于某一颗子树来说我们先统计dis[u]+dis[v]<=k的点的对数,然后把该子树的所有子节点为根的这颗子树中dis[u]+dis[v]<=k的点的对数删去,因为在递归到后面会计算重复(这是第一种情况),所以这样就得到了该子树的满足条件的点对数,那么

【BZOJ1468】Tree [点分治]

Tree Time Limit: 10 Sec  Memory Limit: 64 MB[Submit][Status][Discuss] Description 给你一棵TREE,以及这棵树上边的距离,问有多少对点它们两者间的距离小于等于K. Input 第一行一个n,接下来n-1行边描述管道,按照题目中写的输入,接下来是一个k. Output 仅包括一个整数,表示有多少对点之间的距离小于等于k. Sample Input 7 1 6 13 6 3 9 3 5 7 4 1 3 2 4 20 4

poj1741(点分治)

Problem 题目大意 解题分析 参考程序 1 #include <map> 2 #include <set> 3 #include <stack> 4 #include <queue> 5 #include <cmath> 6 #include <ctime> 7 #include <string> 8 #include <vector> 9 #include <cstdio> 10 #incl

【POJ1987】Distance Statistics ==【POJ1741】 树分治

广告: #include <stdio.h> int main() { puts("转载请注明出处[vmurder]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/44307489"); } 题意&题解 http://blog.csdn.net/vmurder/article/details/44302921 代码:(同一道题) #include <cstdio> #inclu