• [bzoj1468][poj1741]Tree_点分治


    Tree bzoj-1468 poj-1741

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

        注释:$1le nle 4cdot 10^4$,$1le 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(ncdot 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
    ",ans);
    	return 0;
    }
    

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

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

  • 相关阅读:
    操作系统的用户态和内核态
    C++程序编译过程
    大爽Python入门练习题 15 最长字符串
    大爽Python入门练习题 25 二维列表行列与序数关系
    大爽Python入门练习题 16 三个数找中间值
    大爽Python入门练习题 17 最大差值
    大爽Python入门练习题 19 猜结果
    大爽Python入门练习题 11 倒序生成列表
    大爽Python入门练习题 18 字母次数统计
    大爽Python入门练习题 110 猜函数
  • 原文地址:https://www.cnblogs.com/ShuraK/p/8782663.html
Copyright © 2020-2023  润新知