点分治

Posted Winniechen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了点分治相关的知识,希望对你有一定的参考价值。

点分治:

通常用来处理树上路径信息,选出部分,把部分的全部处理更新答案,用部分与部分之间的联系求出整体。

一般,我们选择重心作为分治对象。

对两个不属于统一部份的进行合并的时候,我们通常需要预处理出到分治中心的信息,再对这种信息进行合并。

点分治很多题的关键在于容斥原理,容斥原理通常能够排除掉很多不正确的

BZOJ1468 Tree

存一下每个点到分治中心的距离,双指针扫一下求出所有满足题意的路径,再减去子树中自己本身的

附上代码:

#include <cstdio>
#include <algorithm>
#include <queue>
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdlib>
using namespace std;
#define N 400005
#define ll long long
int head[N],cnt,siz[N],maxx[N],rot,num,ans,n;
ll dep[N],K;
struct node
{
	int to,next,val;
}e[N<<1];
bool vis[N];
void add(int x,int y,int z)
{
	e[++cnt].to=y;
	e[cnt].next=head[x];
	e[cnt].val=z;
	head[x]=cnt;
	return ;
}
void get_root(int x,int from)
{
	maxx[x]=0,siz[x]=1;
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(to1!=from&&(!vis[to1]))
		{
			get_root(to1,x);
			siz[x]+=siz[to1];
			maxx[x]=max(maxx[x],siz[to1]);
		}
	}
	maxx[x]=max(maxx[x],num-siz[x]);
	if(maxx[rot]>maxx[x])rot=x;
}
ll a[N];
int cnt1;
void get_dep(int x,int from)
{
	a[++cnt1]=dep[x];
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(to1!=from&&(!vis[to1]))
		{
			dep[to1]=dep[x]+e[i].val;
			get_dep(to1,x);
		}
	}
}
int calc(int x)
{
	int sum=0;
	cnt1=0;
	get_dep(x,0);
	sort(a+1,a+cnt1+1);
	int h=1,t=cnt1;
	while(h<t)
	{
		if(a[t]+a[h]>K)t--;
		else 
		{
			sum+=t-h;
			h++;
		}
	}
	return sum;
}
void dfs(int x)
{
	dep[x]=0,vis[x]=1;ans+=calc(x);
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(!vis[to1])
		{
			dep[to1]=e[i].val;
			ans-=calc(to1);
			num=siz[to1];
			rot=0;
			get_root(to1,x);
			dfs(rot);
		}
	}
}
char s[2];
int main()
{
	memset(head,-1,sizeof(head));
	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);
	}
	num=n;
	scanf("%d",&K);
	maxx[0]=1<<30;
	get_root(1,0);
	dfs(rot);
	printf("%d\n",ans);
	return 0;
}

BZOJ2152 聪聪可可

求恰好是3的倍数,那么我们存一下到根的距离为%3==0,%3==1,%3==2的方案数,最后合计一下更新答案就好了,注意细节。

附上代码:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 20005
#define ll long long
int head[N],cnt,siz[N],sn,rot,mx[N],n;
ll ans,f[3],dep[N];
bool vis[N];
struct node
{
	int to,next,val;
}e[N<<1];
void add(int x,int y,int z)
{
	e[++cnt].to=y;
	e[cnt].next=head[x];
	e[cnt].val=z;
	head[x]=cnt;
	return ;
}
void getroot(int x,int from)
{
	siz[x]=1,mx[x]=0;
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(to1!=from&&(!vis[to1]))
		{
			getroot(to1,x);
			siz[x]+=siz[to1];
			mx[x]=max(mx[x],siz[to1]);
		}
	}
	mx[x]=max(mx[x],sn-siz[x]);
	if(mx[x]<mx[rot])rot=x;
	return ;
}
void get_dep(int x,int from)
{
	f[dep[x]]++;
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(to1!=from&&(!vis[to1]))
		{
			dep[to1]=(dep[x]+e[i].val)%3;
			get_dep(to1,x);
		}
	}
}
ll calc(int x)
{
	f[0]=0,f[1]=0,f[2]=0;
	get_dep(x,0);
	return (f[0]*f[0])+(2*f[1]*f[2]);
}
void dfs(int x)
{
	dep[x]=0;vis[x]=1;ans+=calc(x);
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(!vis[to1])
		{
			dep[to1]=e[i].val;
			ans-=calc(to1);
			sn=siz[to1];
			rot=0;
			getroot(to1,0);
			dfs(rot);
		}
	}
}
ll gcd(ll a,ll b)
{
	if(!b)return a;
	return gcd(b,a%b);
}
int main()
{
	memset(vis,0,sizeof(vis));
	memset(head,-1,sizeof(head));
	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%3);
		add(y,x,z%3);
	}
	mx[0]=1<<30;
	sn=n;
	getroot(1,0);
	dfs(rot);
	ll t=gcd(ans,n*n);
	printf("%lld/%lld\n",ans/t,n*n/t);
	return 0;
}

BZOJ3697: 采药人的路径

其实这个题挺有趣的,我们可以考虑,如果没有限制2的话,和上道题差不多,那么现在我们考虑加上条件二,什么样的路径可以更新答案呢?我们可以考虑,更新的时候加上一个限制,就是判断是否可以满足有一个中间点,容斥原理减掉多余的,什么时候存在中间点呢?我们就是这条路径的长度包涵在已经有长度的区间中,至于为什么正确,因为容斥的时候会将不正确的排除掉,剩下的就是同样的东西了,关键就在第二问。

附上代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstring>
#include <cstdlib>
using namespace std;
#define N 100005
#define ll long long
int siz[N],mx[N],head[N],cnt,tot,sn,rot,vis[N],n,len;
struct node
{
    int to,next,val;
}e[N<<1];
ll ans,f[N][2],g[N][2];
void add(int x,int y,int z)
{
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void get_root(int x,int from)
{
    siz[x]=1;mx[x]=0;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(to1!=from&&!vis[to1])
        {
            get_root(to1,x);
            siz[x]+=siz[to1];
            mx[x]=max(mx[x],siz[to1]);
        }
    }
    mx[x]=max(mx[x],sn-siz[x]);
    if(mx[rot]>mx[x])rot=x;
}
void calc(int x,int from,int now,int cnt)
{
    if(now==0)
    {
        if(cnt>=2)ans++;
        cnt++;
    }
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(to1!=from&&!vis[to1])
        {
            calc(to1,x,now+e[i].val,cnt);
        }
    }
}
void get_dep(int x,int from,int now,int l,int r)
{
    if(l<=now&&now<=r)
    {
        if(now>=0)f[now][1]++;
        else g[-now][1]++;
    }else
    {
        if(now>=0)f[now][0]++;
        else g[-now][0]++;
    }
    l=min(l,now);r=max(r,now);len=max(max(r,-l),len);
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(!vis[to1]&&to1!=from)
        {
            get_dep(to1,x,now+e[i].val,l,r);
        }
    }
}
void dfs(int x)
{
    vis[x]=1;calc(x,0,0,0);
    get_dep(x,0,0,1,-1),ans+=f[0][1]*(f[0][1]-1)/2;f[0][0]=f[0][1]=0;
    for(int i=1;i<=len;i++)ans+=f[i][0]*g[i][1]+g[i][0]*f[i][1]+f[i][1]*g[i][1],f[i][0]=f[i][1]=g[i][1]=g[i][0]=0;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(!vis[to1])
        {
            len=0;get_dep(to1,0,e[i].val,0,0);ans-=f[0][1]*(f[0][1]-1)/2;f[0][1]=f[0][0]=0;
            for(int i=0;i<=len;i++)ans-=f[i][0]*g[i][1]+g[i][0]*f[i][1]+f[i][1]*g[i][1],f[i][0]=f[i][1]=g[i][1]=g[i][0]=0;
            sn=siz[to1];
            rot=0;get_root(to1,0);
            dfs(rot);
        }
    }
}
int main()
{
    memset(head,-1,sizeof(head));
    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*2-1);
        add(y,x,z*2-1);
    }
    sn=n;mx[0]=n;
    get_root(1,0);
    dfs(rot);
    printf("%lld\n",ans);
    return 0;
}

BZOJ1316: 树上的询问

同样的原理,get_dep之后处理长度,就是需要离线一下,每次处理出所有答案的结果,关键在离线,在线的话时间复杂度会有毛病。

附上代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstring>
#include <cstdlib>
using namespace std;
#define N 10005
#define ll long long
int siz[N],mx[N],head[N],cnt,tot,sn,rot,vis[N],n,len,a[105],dep[N],d[N],Q,p[N];
struct node
{
	int to,next,val;
}e[N<<1];
bool ans[105],b[1000005];
void add(int x,int y,int z)
{
	e[cnt].to=y;
	e[cnt].val=z;
	e[cnt].next=head[x];
	head[x]=cnt++;
}
void get_root(int x,int from)
{
	siz[x]=1;mx[x]=0;
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(to1!=from&&!vis[to1])
		{
			get_root(to1,x);
			siz[x]+=siz[to1];
			mx[x]=max(mx[x],siz[to1]);
		}
	}
	mx[x]=max(mx[x],sn-siz[x]);
	if(mx[rot]>mx[x])rot=x;
}
void get_dep(int x,int from)
{
	d[++tot]=dep[x];
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(to1!=from&&!vis[to1])
		{
			dep[to1]=dep[x]+e[i].val;
			get_dep(to1,x);
		}
	}
}
void calc(int x)
{
	b[0]=1;int cnt1=0;
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(!vis[to1])
		{
			tot=0;dep[to1]=e[i].val;
			get_dep(to1,0);
			for(int j=1;j<=tot;j++)
			{
				for(int k=1;k<=Q;k++)
				{
					if(a[k]>=d[j]&&b[a[k]-d[j]])
					{
						ans[k]=1;
					}
				}
			}
			for(int j=1;j<=tot;j++)
			{
				if(d[j]<=1000000)
				{
					p[++cnt1]=d[j];
					b[d[j]]=1;
				}
			}
		}
	}
	for(int i=1;i<=cnt1;i++)b[p[i]]=0;
}
void dfs(int x)
{
	vis[x]=1;
	get_dep(x,0);
	calc(x);
	for(int i=head[x];i!=-1;i=e[i].next)
	{
		int to1=e[i].to;
		if(!vis[to1])
		{
			sn=siz[to1];
			rot=0;get_root(to1,0);
			dfs(rot);
		}
	}
}
int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&Q);
	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);
	}
	for(int i=1;i<=Q;i++)
	{
		scanf("%d",&a[i]);
		if(!a[i])ans[i]=1;
	}
	sn=n;mx[0]=1<<30;
	get_root(1,0);
	dfs(rot);
	for(int i=1;i<=Q;i++)
	{
		if(ans[i])puts("Yes");
		else puts("No");
	}
	return 0;
}

BZOJ4016: [FJOI2014]最短路径树问题

这个题,我已经无力吐槽了,这个题的难点在读题和求最短路树,Dijkstra找出最短路图,在最短路图上跑以最小字典序的树,就可以了。

calc的时候树形DP求长度为K的最长链,状态f[i][j]表示节点i,选择j个节点的最长链,随便写写,当然,这个东西不用容斥原理了,其实点分治是可以优化一些树形DP的,例如这道题和下一道题。

附上代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <set>
#include <vector>
using namespace std;
#define N 30005
#define ll long long
int ans,ans1,siz[N],mx[N],head1[N],head[N],dep[N],cnt,cnt1,tot,sn,rot,vis[N],n,len,dis[N],K,m,num[N],f[N],g[N],num1[N];
struct node
{
    int to,next,val;
}E[N<<2],e[N<<1];
void add1(int x,int y,int z)
{
    E[cnt1].to=y;
    E[cnt1].val=z;
    E[cnt1].next=head1[x];
    head1[x]=cnt1++;
    return ;
}
void add(int x,int y,int z)
{
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].next=head[x];
    head[x]=cnt++;
    return ;
}
priority_queue<pair<int,int> >q;
vector<int> v[N];
void dijkstra()
{
    memset(dis,0x3f,sizeof(dis));
    dis[1]=0;
    q.push(make_pair(0,1));
    while(!q.empty())
    {
        int x=q.top().second;q.pop();
        if(vis[x])continue;
        vis[x]=1;
        for(int i=head1[x];i!=-1;i=E[i].next)
        {
            int to1=E[i].to;
            if(dis[x]==dis[to1]+E[i].val)
            {
                v[to1].push_back(i^1);
            }
        }
        for(int i=head1[x];i!=-1;i=E[i].next)
        {
            int to1=E[i].to;
            if(dis[to1]>dis[x]+E[i].val)
            {
                dis[to1]=dis[x]+E[i].val;
                q.push(make_pair(-dis[to1],to1));
            }
        }
    }
}
bool cmp(int a,int b)
{
    return E[a].to<E[b].to;
}
bool used[N];
void build(int x)
{
    sort(v[x].begin(),v[x].end(),cmp);
    for(int i=0;i<v[x].size();i++)
    {
        int to1=E[v[x][i]].to;
        if(!used[to1])
        {
            used[to1]=1;
            add(x,to1,E[v[x][i]].val);
            add(to1,x,E[v[x][i]].val);
            build(to1);
        }
    }
}
/*
void prin(int x,int from)
{
    printf("%d\n",x);
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(to1!=from)
        {
            prin(to1,x);
        }
    }
}*/
void get_root(int x,int from)
{
    siz[x]=1;mx[x]=0;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(to1!=from&&!vis[to1])
        {
            get_root(to1,x);
            siz[x]+=siz[to1];
            mx[x]=max(mx[x],siz[to1]);
        }
    }
    mx[x]=max(mx[x],sn-siz[x]);
    if(mx[rot]>mx[x])rot=x;
}
void get_dep(int x,int from)
{
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(to1!=from&&!vis[to1])
        {
            dep[to1]=dep[x]+1;dis[to1]=dis[x]+e[i].val;len=max(dep[to1],len);
            if(dis[to1]>f[dep[to1]])f[dep[to1]]=dis[to1],num[dep[to1]]=1;
            else if(dis[to1]==f[dep[to1]])num[dep[to1]]++;
            get_dep(to1,x);
        }
    }
}
void calc(int x)
{
    int mlen=0;
    g[0]=0,num1[0]=1;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(!vis[to1])
        {
            num[1]=len=dep[to1]=1;dis[to1]=f[1]=e[i].val;get_dep(to1,0);
            for(int j=1;j<=len&&j<=K;j++)
            {
                if(ans<f[j]+g[K-j])ans=f[j]+g[K-j],ans1=num[j]*num1[K-j];
                else if(ans==f[j]+g[K-j])ans1+=num[j]*num1[K-j];
            }
            for(int j=1;j<=len&&j<=K;j++)
            {
                if(g[j]<f[j])g[j]=f[j],num1[j]=num[j];
                else if(g[j]==f[j])num1[j]+=num[j];
            }
            for(int j=1;j<=len&&j<=K;j++)f[j]=-1<<30,num[j]=0;
            mlen=max(mlen,len);
        }
    }
    for(int i=1;i<=mlen&&i<=K;i++)
    {
        g[i]=-1<<30;num1[i]=0;
    }
}
void dfs(int x)
{
    vis[x]=1;
    calc(x);
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(!vis[to1])
        {
            sn=siz[to1];
            rot=0;get_root(to1,0);
            dfs(rot);
        }
    }
}
int main()
{
    memset(head1,-1,sizeof(head1));
    memset(head,-1,sizeof(head));
    scanf("%d%d%d",&n,&m,&K);K--;
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add1(x,y,z);
        add1(y,x,z);
    }
    dijkstra();
    memset(vis,0,sizeof(vis));cnt=0;used[1]=1;build(1);//prin(1,0);
    memset(dis,0,sizeof(dis));memset(f,0xc0,sizeof(f));
    memset(g,0xc0,sizeof(g));g[0]=0,num1[0]=1;
    sn=n;mx[0]=1<<30;
    get_root(1,0);
    dfs(rot);
    printf("%d %d\n",ans,ans1);
    return 0;
}

BZOJ4182: Shopping

这道题非常的神啊,裸上树形DP是nm^2的,如果用二进制拆分的话,还要多一个log,不存在可过性,剪一剪枝能不能过不太清楚,不过看数据的样子,过的可能性不是很大,卡评测什么的可不好。

可以看出,这又是一道有关路径信息的问题,这种情况下,我们可以想点分治能不能做。

因为点分治其实是将树分成许多部分来处理,那么我们只需要知道每次的分治中心的最大值来更新答案就可以了。

证明:如果一部分不经过分治中心,那么就一定在同一个子树中,那么最后一定会得到一个联通块满足这一部分经过它的分治中心。

那么,我们就可以将问题转化为,给你一棵树,要求你求出经过根的一个联通块在满足题意取得最大值。这个就更加好想了一点,树形背包转化为dfs序+背包。状态不变,还是f[i][j]表示dfs序上第i个点,在i-n中选择了j个的最大值。

我们考虑如果选择一个节点,那么必定会选择这个节点的父节点,那么,我们考虑,f[i][j]=f[i+1][j-c[i]]+w[i];那么如果我们不选择这个节点,就必定不会选择这个节点子树中的任何节点,也就是,f[i][j]=max(f[i][j],f[i+siz[i]][j]);

附上代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <set>
#include <vector>
using namespace std;
#define N 505
#define ll long long
struct node
{
    int to,next;
}e[N<<1];
int head[N],cnt,siz[N],vis[N],mx[N],f[N][4005],ans,w[N],c[N],d[N],n,m,rot,sn;
void add(int x,int y)
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
    return ;
}
void get_root(int x,int from)
{
    siz[x]=1,mx[x]=0;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(to1!=from&&!vis[to1])
        {
            get_root(to1,x);
            siz[x]+=siz[to1];
            mx[x]=max(mx[x],siz[to1]);
        }
    }
    mx[x]=max(mx[x],sn-siz[x]);
    if(mx[rot]>mx[x])rot=x;
}
int idx[N],tot,last[N];
void get(int x,int from)
{
    idx[++tot]=x;
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(to1!=from&&!vis[to1])
        {
            get(to1,x);
        }
    }
    last[x]=tot;
}
void calc(int x)
{
    tot=0;get(x,0);
    for(int i=1;i<=tot+1;i++)
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j]=0;
        }
    }
    for(int i=tot;i;i--)
    {
        int t=d[idx[i]]-1;
        for(int j=m;j>=c[idx[i]];j--)
        {
            f[i][j]=f[i+1][j-c[idx[i]]]+w[idx[i]];
        }
        for(int j=1;j<=t;t-=j,j<<=1)
        {
            for(int k=m;k>=j*c[idx[i]];k--)
            {
                f[i][k]=max(f[i][k],f[i][k-j*c[idx[i]]]+w[idx[i]]*j);
            }
        }
        if(t)
        {
            for(int j=m;j>=t*c[idx[i]];j--)
            {
                f[i][j]=max(f[i][j],f[i][j-t*c[idx[i]]]+w[idx[i]]*t);
            }
        }
        for(int j=m;j>=0;j--)f[i][j]=max(f[i][j],f[last[idx[i]]+1][j]);
    }
    ans=max(ans,f[1][m]);
}
void dfs(int x)
{
    vis[x]=1;
    calc(x);
    for(int i=head[x];i!=-1;i=e[i].next)
    {
        int to1=e[i].to;
        if(!vis[to1])
        {
            sn=siz[to1];
            rot=0;
            get_root(to1,0);
            dfs(rot);
        }
    }
}
void init()
{
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    cnt=0;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&w[i]);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&c[i]);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&d[i]);
        }
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            add(x,y);
            add(y,x);
        }
        mx[0]=1<<30;rot=0;ans=0;sn=n;
        get_root(1,0);
        dfs(rot);
        printf("%d\n",ans);
    }
    return 0;
}

  先更新到这里...学完CDQ分治再更新

以上是关于点分治的主要内容,如果未能解决你的问题,请参考以下文章

基于点分治的树分治

POJ 1741 Tree ——点分治

bzoj2599 [ IOI2011] -- 点分治

算法有关点分治的一些理解与看法

POJ 3714 分治/求平面最近点对

分治——最近点对