点分治学习笔记

前言:

\(Point~divide~and~rule\)

使用

淀粉质就是在树上,依靠不停的递归和分治,解决相同的子问题

先来看看模板题: \(Tree\)

就是找树上\(<=K\)的路径有多少

我们可以分两种情况讨论

\(1.\) 经过根节点\(p\)的路径

\(2.\) 不经过根节点\(p\)的路径

第二种情况可以通过递归来处理,我们直接来讨论第一种情况

设当前的树根节点为 \(p\)

显然一条经过\(p\)路径可以由两条到端点为\(p\)的链组成

显然,会出现重复的情况,例如:

(好大...

这两条链组成的已经不是一条路径了,我们需要去除这种非法情况,这里提供两种方法

\(1.\)容斥

我们只需要统计\(p\)树内子节点到\(p\)的距离,排序后,用双指针做,再把\(p\)子节点的非法情况用种方法去除

\(2.~treap\),强烈安利---\(fhq-treap\)

我们只需要把\(p\)树的子树依次处理,对于\(p\)的一棵子树\(y\),先把所有在\(y\)树里的路径长度\(Dis\)统计出来,再在\(treap\)里找\(<=K-Dis\)的个数,最后将这些路径放入\(treap\)

(推荐使用这一种,一般来说大多数题目都需要不用\(treap\),暴力开桶就行了,这种技巧十分重要

这两种方法都要会掌握

统计完成了,那我们怎么递归呢?

这里有一个核心,我们每次选择只选重心来递归,由于中心的子节点\(size\)永远不会超过\(Root~size/2\),所以我们的递归层数不会超过\(log(n)\)

每一次计算是\(nlogn\),递归层数\(log(n)\),总时间复杂度是\(O(nlognlogn)\),巧妙的暴力

\(code:\)(这里用的是第一种去重方法,太懒了)

#include<bits/stdc++.h>
using namespace std;
const long long N=4e4+10;
struct data {
	long long val,stb,to;
} a[N*2];
long long head[N],max_size[N],size[N],vis[N],Root,k,Dis[N],cnt,n,tot,ans,size_sontree;
void insert(long long x,long long y,long long z) {
	a[++tot].to=y;
	a[tot].stb=head[x];
	a[tot].val=z;
	head[x]=tot;
}
long long get_root(long long x,long long fa) {
	size[x]=1;
	max_size[x]=0;
	for(long long i=head[x]; i; i=a[i].stb) {
		long long xx=a[i].to;
		if(xx==fa||vis[xx]) continue;
		get_root(xx,x);
		size[x]+=size[xx];
		max_size[x]=max(max_size[x],size[xx]);
	}
	max_size[x]=max(max_size[x],size_sontree-size[x]);
	if(max_size[x]<max_size[Root]) Root=x;
}
long long find_dis(long long x,long long fa,long long D) {
	Dis[++cnt]=D;
	for(long long i=head[x]; i; i=a[i].stb) {
		long long xx=a[i].to;
		if(xx==fa||vis[xx]) continue;
		find_dis(xx,x,D+a[i].val);
	}
}
long long find_ans(long long x,long long fa,long long D) {
	cnt=0;
	find_dis(x,0,D);
	sort(Dis+1,Dis+cnt+1);
	long long l=1,r=cnt,sum=0;
	while(1) {
		while(Dis[l]+Dis[r]>k&&r) r--;
	if(l>r) break;
		sum+=r-l;
		l++;
	}
	return sum;
}
void slove(long long x) {
	vis[x]=1;
	ans+=find_ans(x,0,0);
	for(long long i=head[x]; i; i=a[i].stb) {
		long long xx=a[i].to;
		if(!vis[xx]) {
			ans-=find_ans(xx,x,a[i].val);
			Root=0;
			size_sontree=size[xx];
			get_root(xx,x);	
			slove(Root);
		}
	}

}
int main() {

	scanf("%lld",&n);
	for(long long i=1,x,y,z; i<=n-1; i++)	{
		scanf("%lld%lld%lld",&x,&y,&z);
		insert(x,y,z);
		insert(y,x,z);
	}

	scanf("%lld",&k);
	max_size[0]=INT_MAX;
	size_sontree=n;
	get_root(1,0);
	slove(1);
	printf("%lld",ans);
}

其他例题

[Noip模拟题]树上路径

找出最小的\(Dis>=S\)即可,由于是取最小值,所以容斥就不行了,这里使用的是\(set\)

\(code:\)(在线格式化不要嫌丑

#include <bits/stdc++.h>
using namespace std;
const int	N = 1e5 + 10;
int		max_size[N], size_sontree, size[N], head[N], Dis[N], vis[N], Root, tot, cnt, n, S, E, ans;
struct data {
	int stb, to, val;
} a[N * 2];
void insert( int x, int y, int z )
{
	a[++tot].to	= y;
	a[tot].stb	= head[x];
	a[tot].val	= z;
	head[x]		= tot;
}


int get_root( int x, int fa )
{
	size[x] = 1, max_size[x] = 0;
	for ( int i = head[x]; i; i = a[i].stb )
	{
		int xx = a[i].to;
		if ( xx == fa || vis[xx] )
			continue;
		get_root( xx, x );
		size[x]		+= size[xx];
		max_size[x]	= max( max_size[x], size[xx] );
	}
	max_size[x] = max( max_size[x], size_sontree - size[x] );
/*	printf("mxsize[%d]=%d\n",x,max_size[x]); */
	if ( max_size[x] < max_size[Root] )
		Root = x;
}


void find_dis( int x, int fa, int D )
{
	Dis[++cnt] = D;
	for ( int i = head[x]; i; i = a[i].stb )
	{
		int xx = a[i].to;
		if ( xx == fa || vis[xx] )
			continue;
		find_dis( xx, x, D + a[i].val );
	}
}


int calc( int x )
{
	cnt = 0;
	set<int>q;
	q.insert( 0 );
	for ( int i = head[x]; i; i = a[i].stb )
	{
		int xx = a[i].to;
		if ( vis[xx] )
			continue;
		cnt = 0;
		find_dis( xx, x, a[i].val );
		for ( int l = 1; l <= cnt; l++ )
		{
/*	cout<<Dis[l]<<" "; */
			set<int>::iterator r = q.lower_bound( S - Dis[l] );
			if ( r == q.end() )
				continue;
			ans = min( ans, Dis[l] + *r );
		}
		for ( int j = 1; j <= cnt; j++ )
			q.insert( Dis[j] );
	}
}


void Slove( int x )
{
	vis[x] = 1;
	calc( x );
/*	printf("Root:%d\n",x); */
	for ( int i = head[x]; i; i = a[i].stb )
	{
		int xx = a[i].to;
		if ( vis[xx] )
			continue;
		Root		= 0;
		size_sontree	= size[xx];
		get_root( xx, x );
		Slove( Root );
	}
}


int main()
{
	ans = INT_MAX;
	scanf( "%d%d%d", &n, &S, &E );
	for ( int i = 1; i <= n - 1; i++ )
	{
		int x, y, z;
		scanf( "%d%d%d", &x, &y, &z );
		insert( x, y, z );
		insert( y, x, z );
	}
	max_size[0]	= INT_MAX;
	size_sontree	= n;
	get_root( 1, 0 );
	Slove( Root );
	if ( ans <= E )
		printf( "%d", ans );
	else printf( "-1" );
}

[黑白配](http://www.forioi.com/p/6547)

这里有两个条件:

\(1.\)路径上黑色边与白色边的数量相同。

\(2.\)路径中存在一个不同于起始点和终点的点,它到终点的路径也满足\(1\)

把黑看成\(1\),白看成\(-1\),两条链的\(Dis\)为相反数即可满足\(1\)条件

讨论第二种条件:

如果一条链\(p->u\)\(Dis\)在这条链中间任意一点\(p->v\)\(Dis\)也出现过,那么\(u->v\)\(Dis\)就为\(0\),我们称这条链为\(can\)链,

处理\(can\)链可以用递归来做,开个\(map\)统计\(Dis\)的出现

我们发现,只要两条链只要有一条\(can\)链,就能满足条件\(2\),开两个\(2维map\)来统计\(can链\)和非\(can链\)的个数,用类似第二种情况去重,还需要特判单独一条链的情况

\(code:\)(此题细节较多)

#include <bits/stdc++.h>
using namespace std;
const long long			N = 1e6 + 10;
long long			max_size[N], size_sontree, size[N], head[N], Dis[N], vis[N], Root, tot, cnt, n, S, E, ans, use[N];
map<long long, long long >	duck;
map<long long, long long >	mp1;
map<long long, long long >	mp0;
struct data {
	long long stb, to, val;
} a[N * 2];
void insert( long long x, long long y, long long z )
{
	a[++tot].to	= y;
	a[tot].stb	= head[x];
	a[tot].val	= z;
	head[x]		= tot;
}


long long get_root( long long x, long long fa )
{
	size[x] = 1, max_size[x] = 0;
	for ( long long i = head[x]; i; i = a[i].stb )
	{
		long long xx = a[i].to;
		if ( xx == fa || vis[xx] )
			continue;
		get_root( xx, x );
		size[x]		+= size[xx];
		max_size[x]	= max( max_size[x], size[xx] );
	}
	max_size[x] = max( max_size[x], size_sontree - size[x] );
	if ( max_size[x] < max_size[Root] )
		Root = x;
}


void find_dis( long long x, long long fa, long long D )
{
	Dis[++cnt] = D;
	if ( duck[D] )
	{
		if ( D == 0 && duck[0] != 1e6 )
			ans++;
		use[cnt] = 1;
	}
	duck[D]++;
	for ( long long i = head[x]; i; i = a[i].stb )
	{
		long long xx = a[i].to;
		if ( xx == fa || vis[xx] )
			continue;
		find_dis( xx, x, D + a[i].val );
	}
	duck[D]--;
}


long long calc( long long x )
{
	cnt = 0;
	int last = 0;
	for ( long long i = head[x]; i; i = a[i].stb )
	{
		long long xx = a[i].to;
		if ( vis[xx] )
			continue;
		duck[0] = 1e6;
		find_dis( xx, x, a[i].val );
		for ( long long l = last + 1; l <= cnt; l++ )
		{
			ans += mp1[-Dis[l]];
			if ( use[l] )
				ans += mp0[-Dis[l]];
		}
		for ( long long l = last + 1; l <= cnt; l++ )
		{
			if ( use[l] )
				mp1[Dis[l]]++;
			else mp0[Dis[l]]++;
		}
		last = cnt;
	}
	for ( long long l = 1; l <= cnt; l++ )
	{
		if ( use[l] )
			mp1[Dis[l]]--, use[l]--;
		else mp0[Dis[l]]--;
	}
}


void Slove( long long x )
{
	vis[x] = 1;
	calc( x );
	for ( long long i = head[x]; i; i = a[i].stb )
	{
		long long xx = a[i].to;
		if ( vis[xx] )
			continue;
		Root		= 0;
		size_sontree	= size[xx];
		get_root( xx, x );
		Slove( Root );
	}
}


int main()
{
	scanf( "%lld", &n );
	for ( long long i = 1; i <= n - 1; i++ )
	{
		long long x, y, z;
		scanf( "%lld%lld%lld", &x, &y, &z );
		if ( z == 0 )
			z = -1;
		insert( x, y, z );
		insert( y, x, z );
	}
	max_size[0]	= INT_MAX;
	size_sontree	= n;
	get_root( 1, 0 );
	Slove( Root );
	printf( "%lld", ans );
}

总结:

其他的直接套模板,主要是去重

\(1.\)找路径<=\(K\),用容斥

\(2.\)找路径\(=K\),暴力用桶

\(3.\)找最值,用\(set\)

\(fhq-treap\)并没卵用

完结撒花

posted @ 2021-02-16 21:51  CJXYY  阅读(64)  评论(0编辑  收藏  举报