zoukankan      html  css  js  c++  java
  • AcWing 252. 树(点分治)

    前置知识:

    淀粉质将一颗树划分为重心以及若干颗子树,对于某个问题而言,划分为两个子问题:
    (1.)问题在同一颗子树的内部,递归下去求解。
    (2.问题在两个子树之间,这时候必然经过重心,涉及到答案的合并,是题目的难点)
    关于为什么要选择重心:每次都将问题的范围缩小为原来的一半,使得总的时间复杂度为(O(nlog^{2}n)).此时的重心保证,除重心所在子树之外的其他子树之和小于总的子树和的一半即可。
    步骤:
    (1.)找出重心,并删除重心
    (2.)求解子问题

    本题思路:

    本题让求长度不超过 (K) 的路径。考虑路径的两个点分别来自哪儿,将问题划分为三类:

    • 路径的两点为不同的两颗子树里,这时路径必然经过重心
    • 路径的两点在同一颗子树里,路径不经过重心
    • 路径的一端为重心,另一端为子树中的点。

    接下来讨论如何求解:
    对于第一种情况,我们可以用(dfs)求出重心到其他子树中所有点的距离,放到一个数组里,问题就转化成了,从数组里任取两个数,使得他们的和(<=k),双指针即可求解;
    那么第一种情况的缺点在哪呢,在用双指针算法求解的时候,无法保证两个点是在不同的子树中的,根据容斥原理,我们要删除第二种情况。维护两个数组,一个表示除了重心之外的所有点到重心的距离,一个表示枚举到的某颗子树里的所有点到重心的距离,就转化成了同样的问题,调用同一个函数就可以求解。
    对于第三种情况,只需要在前面两种情况求解的基础上增加一个特判就好了。
    处理完当前重心后,递归处理每一颗子树,处理过程也同上。

    代码:

    // Problem: 树
    // Contest: AcWing
    // URL: https://www.acwing.com/problem/content/254/
    // Memory Limit: 10 MB
    // Time Limit: 3000 ms
    // 
    // Powered by CP Editor (https://cpeditor.org)
    
    #pragma GCC optimize(1)
    #pragma GCC optimize(2)
    #pragma GCC optimize(3,"Ofast","inline")
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<ll, ll>PLL;
    typedef pair<int, int>PII;
    typedef pair<double, double>PDD;
    #define I_int ll
    inline ll read()
    {
        ll x = 0, f = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
            if(ch == '-')f = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
            x = x * 10 + ch - '0';
            ch = getchar();
        }
        return x * f;
    }
      
    inline void out(ll x){
        if (x < 0) x = ~x + 1, putchar('-');
        if (x > 9) out(x / 10);
        putchar(x % 10 + '0');
    }
      
    inline void write(ll x){
        if (x < 0) x = ~x + 1, putchar('-');
        if (x > 9) write(x / 10);
        putchar(x % 10 + '0');
        puts("");
    }
      
    #define read read()
    #define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
    #define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
    #define rep(i,a,b) for(int i=(a);i<=(b);i++)
    #define repp(i,a,b) for(int i=(a);i<(b);i++)
    #define per(i,a,b) for(int i=(a);i>=(b);i--)
    #define perr(i,a,b) for(int i=(a);i>(b);i--)
    ll ksm(ll a, ll b)
    {
        ll res = 1;
        while(b)
        {
            if(b & 1)res = res * a ;
            a = a * a ;
            b >>= 1;
        }
        return res;
    }
      
    const int maxn=10010,maxm=2*maxn;
    
    int n,m;
    int h[maxn],idx,e[maxm],ne[maxm],w[maxm];
    bool st[maxn];
    int p[maxn],q[maxn],qt;
    
    void add(int u,int v,int ww){
    	e[idx]=v,w[idx]=ww,ne[idx]=h[u],h[u]=idx++;
    }
    
    int get_size(int u,int fa){
    	//求子树大小
    	if(st[u]) return 0;
    	int res=1;
    	for(int i=h[u];~i;i=ne[i]){
    		int v=e[i];
    		if(v!=fa)
    			res+=get_size(v,u);
    	}
    	return res;
    }
    
    int get_wc(int u,int fa,int tot,int &wc){
    	///求重心
    	if(st[u]) return 0;
    	int sum=1,ms=0;
    	for(int i=h[u];~i;i=ne[i]){
    		int v=e[i];
    		if(v!=fa){
    			int tt=get_wc(v,u,tot,wc);
    			ms=max(ms,tt);
    			sum+=tt;	
    		}
    	}
    	ms=max(ms,tot-sum);
    	if(ms<=tot/2) wc=u;
    	return sum;
    }
    
    void get_dist(int u,int fa,int dist,int &qt){//求所有点到重心的距离
    	if(st[u]) return ;
    	q[qt++]=dist;
    	for(int i=h[u];~i;i=ne[i]){
    		int v=e[i];
    		if(v!=fa){
    			get_dist(v,u,dist+w[i],qt);
    		}
    	}
    }
    
    int get(int a[],int k){//双指针求解合法情况
    	sort(a,a+k);
    	int res=0;
    	for(int i=k-1,j=-1;i>=0;i--){
    		while(j+1<i&&a[j+1]+a[i]<=m) j++;
    		j=min(j,i-1);
    		res+=j+1;
    	}
    	return res;
    }
    
    int calc(int u){
    	if(st[u]) return 0; //说明已经被当过重心,已经删除,不考虑
    	int res=0;
    	get_wc(u,-1,get_size(u,-1),u);//求出重心
    	st[u]=true;//删除重心
    	int pt=0;///总的数组的下标清空
    	for(int i=h[u];~i;i=ne[i]){//遍历所有的出边
    		int v=e[i];
    		qt=0;
    		get_dist(v,-1,w[i],qt);//求出所有点到重心的距离
    		res-=get(q,qt);//第二种情况,减去两个端点是同一颗子树里的数量
    		for(int k=0;k<qt;k++){//第三种情况
    			if(q[k]<=m) res++;//一个点为重心
    			p[pt++]=q[k];
    		}
    	}
    	res+=get(p,pt);///第一种情况,加上所有点的合法情况
    	for(int i=h[u];~i;i=ne[i]){
    		int v=e[i];//递归处理每一个子树
    		res+=calc(v);
    	}
    	return res;
    }
    
    int main(){
    	while(cin>>n>>m){//多组输入
    	    if(!n&&!m) break;
    	   // cout<<n<<" "<<m<<endl;
    	    memset(h,-1,sizeof h);//初始化
    	    idx=0;
    		memset(st,0,sizeof st);
    		qt=0;
    		rep(i,1,n-1){//输入边
    			int u=read,v=read,w=read;
    			//cout<<u<<" "<<v<<" "<<w<<endl;
    			add(u,v,w);add(v,u,w);
    		}
    		printf("%d
    ",calc(0));//输出答案
    	}
    	
    	return 0;
    }
    
    
  • 相关阅读:
    our毕业短片
    Android Memory Management, OutOfMemoryError
    android autoswitched ImageSwitcher
    Storage size of Bitmap
    Frequentlyused URI of Intent
    小知识: 软件版本号讲解: 什么是Alpha, Beta, RC
    JSF框架中使用的设计模式介绍
    Unicode编码表/00000FFF
    Spring事务的传播行为和隔离级别
    领略Spring 3.x 时代的Spring MVC
  • 原文地址:https://www.cnblogs.com/OvOq/p/15069095.html
Copyright © 2011-2022 走看看