点分治入门

树上操作总是那么令人愉快。

点分治,针对树上或图上操作的时候,选择一个点,将其分为多棵子树,递归求解并加上子树交集的贡献得到答案。整体仍然是分治的思想。

点分治入门,首先我们面临的第一个问题是:如何选点。

如何选点

如何选点?选什么点?

天知道出题人会用什么千奇百怪的智障数据来卡你,如果,万恶的出题人给了你一条链的话……

以此为例。如果我们按照平常思路选择树根作为分治点,那你要选择n次才能把链算完,复杂度欧恩。(手动狗头)

但是,如果你每次选择链的中点,那就是妥妥的o咯个恩(手动滑稽)

我们再想象一个极端图形:菊花图。就是一个点在中间,一堆点往外散,可以发现,你选择任何一个花瓣(??)来摧残这朵花,远比不上直接把花心给xxx来得快(???)。

得出结论(得出个啥你在口糊什么),我们要选择树上一个点,以这个点为中心分开的两颗子树的较大size最小。

我们通常叫这个点:树的重心。

树的重心

定义上面已经讲了。现在来讲求法。

几乎所有树上维护信息的求法都是dfs(废话呢)。上代码,有讲解。

void getroot(int u,int faa){
	size[u]=1;maxson[u]=0;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];
		if(v==faa||vis[v])continue;
		getroot(v,u);
		size[u]+=size[v];
		maxson[u]=max(maxson[u],size[v]);
	}
	maxson[u]=max(maxson[u],sum-maxson[u]);
	if(maxson[u]<MAX){
		MAX=maxson[u];root=u;
	}
}

我们来分析。getroot的意思是找到这个重心。root就是我们要找的地方,maxson指以这个点为中心分开的两颗子树的较大size最小。(原话复制)

maxson[u]=max(maxson[u],sum-maxson[u]);这句话对应的就是分开的两颗子树中剩下的那个,也就是原树去掉u这个子树剩下的size。(sum会在每次getroot之前更新,初始为n)。MAX初始为INF

所以现在我们找到了这个重心,接下里要做的就是处理。

计算贡献

贡献有两种。

1.同一个子树里面的。

2.不同子树中的。(也就是经过子树根节点的?)

明显1只需要递归下去就行这里我不理他,2才是我们处理的重点。

我喜欢叫分治的主程序divide,统计子树贡献叫solve。所以上代码。

void getdis(int u,int faa,int diss){
	dis[++cnt]=diss;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];
		if(v==faa||vis[v])continue;
		getdis(v,u,diss+w[i]);
	}
}
void solve(int u,int ww,int key){
	cnt=0;
	getdis(u,0,ww);
	for(int i=1;i<cnt;i++){
		for(int j=i+1;j<=cnt;j++){
			judge[dis[i]+dis[j]]+=key;
		}
	}
}
void divide(int u){
	solve(u,0,1);vis[u]=1;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];
		if(vis[v])continue;
		solve(v,w[i],-1);
		sum=size[v];MAX=0x3f3f3f3f;root=0;
		getroot(v,0);
		divide(root); 
	}
}

在main函数中我们会先对原树进行getroot,然后divide(root)。

divide函数的意义就是,统计子树答案,打标记,容斥原理去掉重复答案,重新找子树的分治点,继续divide。

这道题比较特殊,是洛谷模板点分治1,其它大多数时候是ans+=solve(u,0);ans-=solve(v,w[i]);

solve函数因题而异变化比较大,这道题找相同,大多数题求相加大于等于小于等于之类地可以用二分优化。

getdis函数是求在当前子树中所有点到这个子树根节点的距离。这个getdis因题而异也会有变动但一般变化不大。

容斥去重这一步是因为你可能在计算某个祖先的时候已经见过这几个点,再算会重复,所以减掉(??)

总之要去重就对了qwq

甩一个点分治1的程序上来。

#include<bits/stdc++.h>
#define in read()
using namespace std;
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}
	return cnt*f;
}
int n,m,k,ans,vis[10003],cnt,dis[10003],judge[10000003];
int first[10003],nxt[20003],to[20003],w[20003],tot;
int maxson[10003],sum,size[10003],root,MAX;
void add(int a,int b,int c){
	nxt[++tot]=first[a];first[a]=tot;to[tot]=b;w[tot]=c;
}
void getroot(int u,int faa){
	size[u]=1;maxson[u]=0;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];
		if(v==faa||vis[v])continue;
		getroot(v,u);
		size[u]+=size[v];
		maxson[u]=max(maxson[u],size[v]);
	}
	maxson[u]=max(maxson[u],sum-maxson[u]);
	if(maxson[u]<MAX){
		MAX=maxson[u];root=u;
	}
}
void getdis(int u,int faa,int diss){
	dis[++cnt]=diss;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];
		if(v==faa||vis[v])continue;
		getdis(v,u,diss+w[i]);
	}
}
void solve(int u,int ww,int key){
	cnt=0;
	getdis(u,0,ww);
	for(int i=1;i<cnt;i++){
		for(int j=i+1;j<=cnt;j++){
			judge[dis[i]+dis[j]]+=key;
		}
	}
}
void divide(int u){
	solve(u,0,1);vis[u]=1;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];
		if(vis[v])continue;
		solve(v,w[i],-1);
		sum=size[v];MAX=0x3f3f3f3f;root=0;
		getroot(v,0);
		divide(root); 
	}
}
int main(){
	n=in;m=in;sum=n;
	for(int i=1,a,b,c;i<n;i++){
		a=in;b=in;c=in;
		add(a,b,c);add(b,a,c);
	}ans=0;
	MAX=0x3f3f3f3f;sum=n;
	getroot(1,0);
	divide(root);
	for(int i=1,k;i<=m;i++){
		k=in;if(judge[k])printf("AYE\n");
	else printf("NAY\n");
	}return 0;
}

推荐题目

 

点分治1

聪聪可可

然后在洛谷还有很多,,

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值