zoukankan      html  css  js  c++  java
  • 点分治&点分树 复习

    点分治&点分树复习

    点分治

    点分治是处理树上路径问题的一种通用的方式,是树分治的一种。

    从最一般的问题来入手,有这样一个经典的问题:

    例题1

    Tree

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

    如果我们直接求所有点对,那么复杂度是(O(n^2)) 的,对于这道题 (nle4 imes10^4)来说,是无法接受的。

    而这道题的问题只是求距离的数量,和具体的点没关系,也就是说,我们只需要知道距离的集合即可。

    想到一个点一个点的考虑,确定一个点,然后枚举所有的子树,求出所有到这个点的距离的集合,在这些距离中,已经有满足(disle k) 的,且还有可以组合起来仍然(le k) 的,先不管它们怎么组合,我们先考虑通过这样的方法我们做到了什么,这样的操作相当于通过这个点能够找到的距离已经全部被处理过了,且经过这个点的两个不同子树内的点也被考虑过了,所以我们可以只单独的考虑每个子树内的内容,而不用再考虑和这个点有关的路径,然后我们可以递归处理每个子树内的情况,当然这样的操作还不能降低复杂度,因为递归下去可能遇到的还是一颗大小近似(n)的子树,那怎么办?遇到问题就解决问题,既然可能遇到一颗大小近似(n)的子树,那就提前让确定的这个点的子树都不超过(n/2) 即我们找到树的中心,然后如果我们能够(O(n)) 解决我们组合一个点所搜索到的(dis) ,那么我们就可以(T(n)=T(n/2)+n) 即复杂度(O(nlogn))的得到最终的结果;即使是(O(nlogn))的解决每次的面临的问题,最终的复杂度也只是(O(nlog^2n))

    这就是点分治,可以概括为三个步骤:

    1 找到树的重心,统计有关重心的路径信息。

    2 统计所有路径信息,不同子树间进行归并后计算,直接和重心相连的路径直接计算。

    3 删除重心,递归到子树做同样的事情。

    听起来是不难的,确实,分治的思想是不困难的,对于点分治来说困难的是:中间归并的操作。

    这道模板题就需要点小小的技巧,首先是对于和重心相连的点,(disle k) 的直接计入答案,然后将所有子树的dis计入到一个数组中,将数组排序之后,考虑使用双指针,一个在头,一个在尾,二者相加如果(le k) 的话,那么所有的左指针前的点和右指针这个点的dis之和都是(le k) 的,然后左指针右移;反之,如果二者相加大于k,那么右指针应该向左移动;正确性:我们是从边界开始考虑的,所以说一开始的(l)如果不能满足(r) ,那么(r) 只能左移,这个时候(l) 如果满足了新的(r),那么也一定能够满足向左移动的(r) ,然后我们可以尝试让(l) 向右移动看看能不能满足更多的(l) ,就这样,这样是满足一个单调性的,而两个指针如果相遇了,那么只要(r) 一直向左推就好。还有一个问题就是,这个过程中可能出现同一颗子树到重心的dis,它们是不能被组合的,所以我们在搜索每一颗子树的dis的时候,就先对着这颗子树内的dis进行一波(le k)的组合的统计,然后直接让ans减去这些答案即可,收工!。
    具体代码如下

    点击此处展开和收起代码
    #include<bits/stdc++.h>
    #define reg register
    typedef long long ll;
    using namespace std;
    inline int qr(){
    	int x=0,f=0;char ch=0;
    	while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return f?-x:x;
    }
    const int maxn=2e4+100;
    int n,m;
    struct node{
    	int next,to,cost;
    }edge[maxn];
    int head[maxn],_tot;
    bool o[maxn];
    int p[maxn],q[maxn];//p存当前所有子树内的点到重心的距离,q数组存当前子树内所有点到重心的距离 
    void add(int u,int v,int l){edge[_tot].next=head[u];edge[_tot].to=v;edge[_tot].cost=l;;head[u]=_tot++;}
    int get_size(int x,int fa){//求得子树大小  
    	if(o[x]) return 0;
    	int res=1;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		res+=get_size(y,x);
    	}
    	return res;
    }
    int get_wc(int x,int fa,int tot,int &wc){//求树的伪重心  tot表示当前节点的子树大小  
    	if(o[x]) return 0;
    	int sum=1,maxx=0;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		int t=get_wc(y,x,tot,wc);
    		maxx=max(maxx,t);
    		sum+=t;
    	}
    	maxx=max(maxx,tot-sum);
    	if(maxx<=tot/2) wc=x;
    	return sum;//返回子树的大小 
    }
    void get_dis(int x,int fa,int dis,int &qt){//求到重心的距离 
    	if(o[x]) return ;
    	q[qt++]=dis;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		get_dis(y,x,dis+edge[i].cost,qt);
    	}
    }
    int get(int a[],int k){//用双指针算法求一个序列中两数相加小于等于k的数的数量 
    	sort(a,a+k);
    	int  res=0;
    	for(reg 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 x){
    	if(o[x]) return 0;
    	int res=0;
    	get_wc(x,-1,get_size(x,-1),x);
    	o[x]=1;//删除重心 
    	int len=0;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to,qt=0;
    		get_dis(y,-1,edge[i].cost,qt);
    		res-=get(q,qt);
    		for(reg int k=0;k<qt;k++){
    			if(q[k]<=m) res++;
    			p[len++]=q[k];
    		}
    	}
    	res+=get(p,len);
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		res+=calc(y);
    	}
    	return res;
    }
    int main(){
    //	freopen("poj1741_tree.in","r",stdin);
    //	freopen("poj1741_tree.out","w",stdout);
    	n=qr();
    	memset(head,-1,sizeof(head));
    	memset(o,0,sizeof(o));
    	_tot=0;
    	for(reg int i=1;i<n;i++){
    		int u=qr()-1,v=qr()-1,l=qr();
    		add(u,v,l);add(v,u,l);
    	}
    	m=qr();
    	printf("%d
    ",calc(0));
    	return 0;
    }
    /*
    点分治 对树上的点进行分治,递归归并求解以达到nlog^2n 的复杂度 
    针对一棵树先找到树的伪重心(任意子树大小小于1/2即可),然后针对每一棵子树递归
    继续求解,然后对子树内容进行归并求解 
    */
    

    例题2

    再介绍一道 :Race/权值

    给定一颗大小为 (N) 的树,求权值为 (k) 的经过边数最小的路径,不存在则输出-1,(Nle2 imes10^{5},Kle10^6)

    考虑DP,(f[i]) 表示权值为 (i) 的路径最短边数是多少,然后我们按照类似的过程:

    枚举所有方案 ,首先 找到树的重心,然后开始分治
    1 两个点都在某个子树内,递归下去求解
    2 有一个点是重心,求每个点到重心的距离即可
    3 两个点分别在不同的子树内,开一个桶,i存到重心的距离是i的所有点中边数最小的点
    所以枚举一个子树内有dis==x,考虑其它子树的桶内k-x的点即可
    和上一道题一模一样,基本都是考虑这三种情况 。

    点击此处展开和收起代码
    #include<bits/stdc++.h>
    #define reg register 
    #define X first
    #define y second
    typedef long long ll;
    using namespace std;
    inline int qr(){
    	int x=0,f=0;char ch=0;
    	while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return f?-x:x;
    }
    const int maxn=2e5+100;
    const int M=1e6+100;
    const int inf=0x3f3f3f3f;
    int n,m;
    struct node{
    	int next,to,cost;
    }edge[maxn<<1];
    int head[maxn],_tot;
    void add(int u,int v,int l){edge[_tot].next=head[u];edge[_tot].to=v;edge[_tot].cost=l;head[u]=_tot++;}
    pair<int,int>q[maxn],p[maxn];
    int f[M];
    bool o[maxn];
    int ans;
    int get_size(int x,int fa){
    	if(o[x]) return 0;
    	int res=1;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		res+=get_size(y,x);
    	}
    	return res;
    }
    int get_wc(int x,int fa,int tot,int &wc){
    	if(o[x]) return 0;
    	int sum=1,maxx=0;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa ) continue;
    		int t=get_wc(y,x,tot,wc);
    		maxx=max(maxx,t);
    		sum+=t;
    	}
    	maxx=max(maxx,tot-sum);//父亲也得算  
    	if(maxx<=tot/2) wc=x;
    	return sum;
    }
    void get_dis(int x,int fa,int dis,int cnt,int &qt){//cnt表示经过的边的数量 
    	if(o[x]||dis>m) return ;//小剪枝 
    	q[qt++]=make_pair(dis,cnt);
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		get_dis(y,x,dis+edge[i].cost,cnt+1,qt);
    	}
    }
    void calc(int x){
    	if(o[x])return;
    	get_wc(x,-1,get_size(x,-1),x);
    	o[x]=1;
    	int len=0;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to,qt=0;
    		get_dis(y,-1,edge[i].cost,1,qt);
    		for(reg int k=0;k<qt;k++){//归并 
    			pair<int,int> t =q[k]; 
    			if(t.X==m) ans=min(ans,t.y);//对于直接等于m的点,比较ans 
    			ans=min(ans,f[m-t.X]+t.y);//否则去桶里找 
    			p[len++]=t;//统计一下整个联通块的合法点  
    		}
    		for(reg int k=0;k<qt;k++){
    			pair<int,int >t =q[k];
    			f[t.X]=min(f[t.X],t.y);
    		}//把自己放在桶里 
    	}
    	for(reg int i=0;i<len;i++){//清空 
    		f[p[i].X]=inf;
    	}
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		calc(y);
    	}
    }
    int main(){
    //	freopen("test.in","r",stdin);
    //	freopen("test.out","w",stdout);
    	n=qr();m=qr();
    	memset(f,0x3f,sizeof(f));
    	ans=inf;
    	memset(head,-1,sizeof(head));
    	for(reg int i=1;i<n;i++){
    		int u=qr(),v=qr(),l=qr();
    		add(u,v,l);add(v,u,l);
    	}
    	calc(0);
    	if(ans==inf) ans=-1;
    	printf("%d
    ",ans);
    	return 0;
    }
    

    例题3

    看一道比较复杂的

    [SPOJ1825] 免费旅行II

    在两周年纪念日的旅行之后,在第三年,旅行社SPOJ又一次踏上的打折旅行的道路。

    这次旅行是ICPC岛屿上进行的,一个位于太平洋上,不可思议的小岛。我们列出了N个地点(编号从1到N)供旅客游览。这N个点由N-1条边连成一个树,每条边都有一个权值,这个权值可能为负。我们可以选择两个地点作为旅行的起点和终点。

    由于当地正在庆祝节日,所以某些地方会特别的拥挤(我们称这些地方为拥挤点)。旅行的组织者希望这次旅行最多访问K个拥挤点。同时,我们希望我们经过的道路的权值和最大。

    其中(nle 2 imes10^5),M,K与N同阶

    确认是点分治之后,主要考虑如何利用信息进行归并,我们需要的是路径的长度(经过的边数)以及路径上的点的数量,需要达成的目的是满足点的数量的同时取路径最长,和上一道题类似,上一道是满足权值求最小边,所以我们设置的状态是 (f[i]) 表示权值为 (i) 然后状态的属性是 (max) 边的数量,那么这道题仍然考虑DP,设 (f[i]) 表示当前子树内经过 (i) 个拥挤点路径的最长值,(g[i]) 表示已经遍历过的子树内经过 (i) 个拥挤点路径的最长值,那么我们在确定好重心之后,对于每个(f[i]) 考虑已经处理过的子树内$0 (~)K-i$ 的状态,二者相加来更新ans,怎么考虑这个(0)~(K-i) 的状态?本人采用了ST表的办法,这样每次询问就是(O(1))的,统计过答案之后更新 (g) 数组(再次建表),每次建表是(O(nlogn)) 所以总复杂度(O(nlog^2n))

    可能ST表复杂度比较高。而题目(Nle2 imes10^5) ,所以(O(nlog^2n))太大了,所以代码里对于(K==M)的部分采用了树形DP。

    点击此处展开和收起代码
    #include<bits/stdc++.h>
    #define reg register 
    typedef long long ll;
    using namespace std;
    inline int qr(){
    	int x=0,f=0;char ch=0;
    	while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return f?-x:x;
    }
    const int maxn=2e5+100;
    int n,K,M;//点数,忍受数,拥挤点数 
    struct node{
    	int next,to,cost;
    }edge[maxn<<1];
    int head[maxn],_tot;
    void add(int u,int v,int l){edge[_tot].next=head[u];edge[_tot].to=v;edge[_tot].cost=l;head[u]=_tot++;}
    ll f[maxn],g[maxn];
    ll F[5000000][22];
    bool o[maxn];
    bool sb[maxn];//拥挤的点 
    int mx,maxs;
    ll ans;
    int get_size(int x,int fa){
    	if(o[x]) return 0;
    	int res=1;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		res+=get_size(y,x);
    	}
    	return res;
    }
    int get_wc(int x,int fa,int tot,int &wc){
    	if(o[x]) return 0;
    	int sum=1,maxx=0;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		int t=get_wc(y,x,tot,wc);
    		sum+=t;
    		maxx=max(maxx,t);
    	}
    	maxx=max(maxx,tot-sum);
    	if(maxx<=tot/2) wc=x;
    	return sum;
    }
    int cnt;
    ll fp,G;
    void get_dis(int x,int fa,ll dis){//cnt表示经过了几个拥挤点 
    	fp=max(fp,dis);
    	if(o[x]) return ;
    	if(sb[x]&&cnt>=K) return;
    	if(sb[x]) cnt++,f[cnt]=max(dis,f[cnt]);
    	else f[cnt]=max(f[cnt],dis);
    	maxs=max(cnt,maxs);
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		get_dis(y,x,dis+edge[i].cost);
    	}
    	if(sb[x]) cnt--;
    }
    
    void st(){
        for(int i = 0; i <= mx+1; i ++) F[i][0] = g[i]; 
        int t = log(n) / log(2) + 1;
        for(int j = 1; j < 20; j ++){
            for(int i = 0; i <= mx - (1 << j) +1; i ++){
                F[i][j] = max(F[i][j-1],F[i + (1 << (j - 1))][j - 1]);
            }
        }
    }
    int query(int x, int y)
    {
        int t = log(abs(y-x +1))/ log(2);
        int a = F[x][t];
        int b = F[y - (1 << t) +1][t];
        return max(a,b);
    }
    ll dp1[maxn],dp2[maxn];
    void calc(int x){
    	if(o[x]) return;
    	if(K==M) {//特判树形DP
    		o[x]=1;G=0;
    		for(reg int i=head[x];~i;i=edge[i].next){
    			int y=edge[i].to;
    			if(o[y]) continue;
    			calc(y);
    			if(dp1[x]<dp1[y]+edge[i].cost){
    				dp2[x]=dp1[x];dp1[x]=dp1[y]+edge[i].cost;
    			}else if(dp2[x]<dp1[y]+edge[i].cost){
    				dp2[x]=dp1[y]+edge[i].cost;
    			}
    			ans=max(dp1[x]+dp2[x],ans);
    		}
    		return;
    	}
    	get_wc(x,-1,get_size(x,-1),x);
    	o[x]=1;mx=0;
    	if(sb[x])K--;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(o[y]) continue;
    		maxs=0;cnt=0;
    		get_dis(y,x,edge[i].cost);
    		mx=max(maxs,mx);
    		st();//重新建表
    		for(reg int j=0;j<=maxs;j++){
    			ans=max(ans,f[j]);
    			ans=max(ans,f[j]+query(0,min(mx,max(0,K-j))));//查询区间最大值
    		}
    		for(reg int j=0;j<=maxs;j++) {
    			g[j]=max(g[j],f[j]);f[j]=0;
    		}
    	}
    	if(sb[x]) K++;
    	for(reg int i=0;i<=mx;i++) g[i]=0;//清空 
    	for(reg int i=head[x];~i;i=edge[i].next) calc(edge[i].to);
    }
    int main(){
    	freopen("freetourII.in","r",stdin);
    	freopen("freetourII.out","w",stdout);
    	n=qr(),K=qr(),M=qr();
    	memset(head,-1,sizeof(head));
    	for(reg int i=1;i<=M;i++){
    		int x=qr(); sb[x]=1;
    	}
    	for(reg int i=1;i<n;i++){
    		int u=qr(),v=qr(),l=qr();
    		add(u,v,l);add(v,u,l);
    	}
    	calc(1);
    	printf("%lld",ans);
    	return 0;
    }
    /*
    找到一条最长路径,其经过的拥挤点不超过K个
    设f[i]为经过i个拥挤点,到达重心的最长距离,每次合并所有子树的时候只需要计算f[i+j]中最大的即可 i+j<=K 
    */
    

    到这里基本印证了先前的说法,点分治的模板框架并不难,每道题的核心都在于归并。

    例题4

    shopping

    马上就是小苗的生日了,为了给小苗准备礼物,小葱兴冲冲地来到了商店街。商店街有 (n)个商店,并且它们之间的道路构成了一棵树的形状。

    (i) 个商店只卖第 (i)种物品,小苗对于这种物品的喜爱度是 (w_i),物品的价格为$ c_i$,物品的库存是 (d_i)。但是商店街有一项奇怪的规定:如果在商店 (u,v)买了东西,并且有一个商店 (p)(u)(v)的路径上,那么必须要在商店 (p)买东西。小葱身上有 (m)元钱,他想要尽量让小苗开心,所以他希望最大化小苗对买到物品的喜爱度之和。

    这种小问题对于小葱来说当然不在话下,但是他的身边没有电脑,于是他打电话给同为OI选手的你,你能帮帮他吗?

    (Nle500;m,w_i,c_ile 4000;d_ile100;c_ile m;) 多测 (Tle5)

    假如问题放在序列上,那么这道题就是一个简单的多重背包,放在树上就需要考虑树形背包,一般的树形背包是(f[i])表示子树下的状态,但这题限制了选择了两个点之后,两点之间的路径上所有商店都至少要选择一个商品,这样就出现了"树上路径"问题,考虑另一种状态表示,(f[i][j])表示考虑了dfn序 (i) ~ (n) 的物品,背包占用为 (j) 的最大价值,我们先肯定我们一定要选择重心,不选择重心的情况我们递归考虑,然后我们求出从重心出发每个点的dfn序,并每个点记录一个out表示子树内dfn序的最大值,然后我们倒序考虑dfn序,即从深到浅考虑背包,并且将每个点必选一个的状态强行放入,这样我们每个点就能够"拓扑的"考虑到它被强迫选择的情况(因为一定要选择重心,所以假如有选择了更深处的点,那么一定会选择较浅的点),并更新背包的状态,当然对于一个子树可以完全不选择以达到更优,这个时候我们的out就可以帮助我们将(f) 带回到采用这个子树前的状态,然后"纠正"背包的状态,背包需要采用二进制分组来优化,这样复杂度就可以做到 (O(nmlog d log n)) 了。

    点击此处展开和收起代码
    #include<bits/stdc++.h>
    #define reg register 
    #define mp make_pair
    typedef long long ll;
    using namespace std;
    inline int qr(){
    	int x=0,f=0;char ch=0;
    	while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return f?-x:x;
    }
    const int N=4140;
    int n,m,ans;
    int w[N],c[N],d[N];
    int dfn[N],dfncnt,out[N];
    int f[N][N];//考虑了dfn序列i~n的背包 
    bool o[N];
    struct node{
    	int next,to;
    }edge[N<<1];
    int head[N],_tot;
    inline void add(int u,int v){
    	edge[_tot]=(node){head[u],v},head[u]=_tot++;
    }
    pair<int,int>p[N];
    int get_size(int x,int fa){
    	if(o[x]) return 0;
    	int sum=1;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		sum+=get_size(y,x);
    	}	
    	return sum;
    }
    int get_wc(int x,int fa,int tot,int &wc){
    	if(o[x]) return 0;
    	int res=1,maxson=-1;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		int c=get_wc(y,x,tot,wc);
    		res+=c;
    		maxson=max(c,maxson);
    	}
    	maxson=max(tot-maxson,maxson);
    	if(maxson<=tot/2) wc=x;
    	return res;
    }
    void get_dfn(int x,int fa){
    	if(o[x]) return;
    	dfn[++dfncnt]=x;
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		if(y==fa) continue;
    		get_dfn(y,x);
    	}out[x]=dfncnt;
    }
    void calc(int x){
    	if(o[x]) return;
    	get_wc(x,0,get_size(x,0),x);
    	dfncnt=0;
    	get_dfn(x,0);
    	o[x]=1;
    	for(reg int i=dfncnt;i;i--){
    		int s=d[dfn[i]]-1,num=0;
    		for(reg int j=1;j<=s;s-=j,j<<=1) p[++num]={w[dfn[i]]*j,c[dfn[i]]*j};//二进制分组 
    		if(s) p[++num]={w[dfn[i]]*s,c[dfn[i]]*s};
    		for(reg int j=m;j>=c[dfn[i]];j--){//强行规定必选一个i的状态 
    			f[i][j]=f[i+1][j-c[dfn[i]]]+w[dfn[i]];
    		}
    		for(reg int k=1;k<=num;k++){//再考虑之前的所有物品 
    			for(reg int j=m;j>=p[k].second;j--){
    				f[i][j]=max(f[i][j],f[i][j-p[k].second]+p[k].first);
    			}
    		}
    		for(reg int j=0;j<=m;j++){//不选择这颗子树,考虑子树外,相当于尝试纠正刚刚考虑强塞这颗子树的点的错误状态 
    			f[i][j]=max(f[i][j],f[out[dfn[i]]+1][j]);
    		}
    	}
    	ans=max(ans,f[1][m]);
    	for(reg int i=0;i<=dfncnt;i++){
    		for(reg int j=0;j<=m;j++){
    			f[i][j]=0;
    		}
    	}
    	for(reg int i=head[x];~i;i=edge[i].next){
    		int y=edge[i].to;
    		calc(y);
    	}
    }
    void clear(){
    	memset(head,-1,sizeof head);
    	memset(o,0,sizeof o);
    	_tot=ans=dfncnt=0;
    }
    int main(){
    	freopen("shopping.in","r",stdin);
    	freopen("shopping.out","w",stdout);	 
    	int T=qr();
    	while(T--){
    		clear();
    		n=qr(),m=qr();
    		for(reg int i=1;i<=n;i++) w[i]=qr();
    		for(reg int i=1;i<=n;i++) c[i]=qr();
    		for(reg int i=1;i<=n;i++) d[i]=qr();	
    		for(reg int i=1;i<n;i++){
    			int u=qr(),v=qr();
    			add(u,v);add(v,u);
    		}
    		calc(1);
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    
    
    

    点分治小总结

    *思考,在什么情况下使用点分治而不是其他的大数据结构,树形DP之类的算法。

    1 树上路径问题,求点对之类的问题,这类问题比较容易点分。

    2 对于确定一个点之后,其它点和它的关系所产生的信息能够在(O(nlogn))或者一个再乘(logn)不会T的复杂度之内解决的(一般来说不会是(n^2),因为 (n^2) 大概率是暴力统计点对),且不需要再考虑这个点的信息的问题。

    点分树

    (待更)

  • 相关阅读:
    序列化的作用
    mysql语法
    SharePoint 普通Application页面设置匿名访问
    SharePoint 2013 运行VS控制台应用程序出现“Class not registered”错误
    SharePoint 计算列比较大小
    SharePoint 数据库迁移步骤
    SharePoint 开启Session
    SharePoint CAML判断一个列表项是否有附件
    SharePoint 母版页保存后不起作用
    SharePoint 备份还原站点集(Site Collection)
  • 原文地址:https://www.cnblogs.com/mikuo/p/14762224.html
Copyright © 2011-2022 走看看