点分治总结

Posted zhoushuyu

tags:

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

为了避免变量名指代不清的问题,我们先规定一下各变量的含义。

const int N = 10005;
struct edge{int to,next,w;}a[N<<1];//边集数组
int n,k,head[N],cnt;//n,k不解释,head[]和cnt是边集数组的辅助变量 
int root,sum;//当前找到的根,当前递归这棵树的大小 
int vis[N];//某一个点是否被当做根过 
int sz[N];//每个点下面的大小 
int f[N];//每个点为根时的最大子树大小 
int dep[N];//每个点的深度 
int o[N];//每个点的深度(用于排序) (这个是以poj1741为例,其他题目不一定要用到这个)
int ans;//最终统计的答案 

点分治的核心是找一个点作为根,而找出来的这个点就是我们所说的“重心”。

void getroot(int u,int fa)
{
    sz[u]=1;f[u]=0;
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (v==fa||vis[v]) continue;
        getroot(v,u);
        sz[u]+=sz[v];f[u]=max(f[u],sz[v]); 
    }
    f[u]=max(f[u],sum-sz[u]);
    if (f[u]<f[root]) root=u;
}

每次找出一个根以后,所有点对就只有两种可能了:
1、两个点都在根的某一棵子树中,即路径不过根;
2、两个点在根的不同子树中,或其中一个点就是根,此时路径必过根。
对于case1显然递归处理,对于case2在此处计算。
没什么好说的看代码吧。

POJ1741 Tree

http://poj.org/problem?id=1741
给出一棵树,给一个K值,求一共有多少点对满足\(dis(u,v)<=K\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 10005;
struct edge{int to,next,w;}a[N<<1];
int n,m,k,head[N],cnt,root,sum,vis[N],sz[N],f[N],dep[N],o[N],ans;
int gi()
{
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
void getroot(int u,int fa)
{
    sz[u]=1;f[u]=0;
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (v==fa||vis[v]) continue;
        getroot(v,u);
        sz[u]+=sz[v];
        f[u]=max(f[u],sz[v]);
    }
    f[u]=max(f[u],sum-sz[u]);
    if (f[u]<f[root]) root=u;
}
void getdeep(int u,int fa)
{
    o[++cnt]=dep[u];
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (v==fa||vis[v]) continue;
        dep[v]=dep[u]+a[e].w;getdeep(v,u);
    }
}
int calc(int u,int d0)
{
    cnt=0;dep[u]=d0;
    getdeep(u,0);
    sort(o+1,o+cnt+1);
    int l=1,r=cnt,res=0;
    while (l<r)
        if (o[l]+o[r]<=k) res+=r-l,l++;
        else r--;
    return res;
}
void solve(int u)
{
    ans+=calc(u,0);
    vis[u]=1;
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (vis[v]) continue;
        ans-=calc(v,a[e].w);
        sum=sz[v];root=0;
        getroot(v,0);
        solve(root);
    }
}
int main()
{
    while (1)
    {
        n=gi();k=gi();
        if (n==0&&k==0) return 0;
        memset(head,0,sizeof(head));
        memset(vis,0,sizeof(vis));
        cnt=0;ans=0;
        for (int i=1,u,v,w;i<n;i++)
        {
            u=gi();v=gi();w=gi();
            a[++cnt]=(edge){v,head[u],w};head[u]=cnt;
            a[++cnt]=(edge){u,head[v],w};head[v]=cnt;
        }
        root=0;sum=f[0]=n;
        getroot(1,0);
        solve(root);
        printf("%d\n",ans);
    }
}

Luogu3806 【模板】点分治1

https://www.luogu.org/problemnew/show/P3806
这次是询问距离为k的点对是否存在。
这种询问就很烦,但是发现了一点:
我们每次把原问题至少分成了两份\(n/2\)大小的子问题,如果我们每次划分的时候,对当前大小的问题做一个\(O(n^2)\)的操作,那么最终的复杂度回与\(O(n^2)\)同级,但小于\(O(n^2)\)
因为点分治的题一般都是\(n\leq10000\),所以这个复杂度是可!以!过!的!
然后poj1741写这种复杂度会TLE。。。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 10005;
struct edge{int to,next,w;}a[N<<1];
int n,m,k,head[N],cnt,root,sum,vis[N],sz[N],f[N],dep[N],o[N],ans[10000005];
int gi()
{
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
void getroot(int u,int fa)
{
    sz[u]=1;f[u]=0;
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (v==fa||vis[v]) continue;
        getroot(v,u);
        sz[u]+=sz[v];f[u]=max(f[u],sz[v]); 
    }
    f[u]=max(f[u],sum-sz[u]);
    if (f[u]<f[root]) root=u;
}
void getdeep(int u,int fa)
{
    o[++cnt]=dep[u];
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (v==fa||vis[v]) continue;
        dep[v]=dep[u]+a[e].w;getdeep(v,u);
    }
}
void calc(int u,int d0,int add)
{
    cnt=0;dep[u]=d0;
    getdeep(u,0);
    for (int i=1;i<=cnt;i++)
        for (int j=1;j<=cnt;j++)
            ans[o[i]+o[j]]+=add;
}
void solve(int u)
{
    calc(u,0,1);vis[u]=1;
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (vis[v]) continue;
        calc(v,a[e].w,-1);
        sum=sz[v];root=0;
        getroot(v,0);
        solve(root);
    }
}
int main()
{
    n=gi();m=gi();
    for (int i=1,u,v,w;i<n;i++)
    {
        u=gi();v=gi();w=gi();
        a[++cnt]=(edge){v,head[u],w};head[u]=cnt;
        a[++cnt]=(edge){u,head[v],w};head[v]=cnt;
    }
    sum=f[0]=n;
    getroot(1,0);
    solve(root);
    for (int i=1;i<=m;i++)
        k=gi(),puts(ans[k]?"AYE":"NAY");
    return 0;
}

聪聪可可

https://www.luogu.org/problemnew/show/2634
给一棵树,求这棵树上任选两个点(注意可以相同)使得它们距离为3的倍数的概率。
点分治每次求出到当前根距离除以3余0,1,2的点的数量\(t_0t_1t_2\)然后答案就是\(t_0*t_0+2*t_1*t_2\)
概率就是总方案数除以\(n^2\)再约个分。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 20005;
struct edge{int to,next,w;}a[N<<1];
int n,head[N],cnt,root,sum,vis[N],sz[N],f[N],dep[N],t[3],ans;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int gi()
{
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
void getroot(int u,int fa)
{
    sz[u]=1;f[u]=0;
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (v==fa||vis[v]) continue;
        getroot(v,u);
        sz[u]+=sz[v];f[u]=max(f[u],sz[v]);
    }
    f[u]=max(f[u],sum-sz[u]);
    if (f[u]<f[root]) root=u;
}
void getdeep(int u,int fa)
{
    t[dep[u]%3]++;
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (v==fa||vis[v]) continue;
        dep[v]=dep[u]+a[e].w;getdeep(v,u);
    }
}
int calc(int u,int d0)
{
    dep[u]=d0;t[0]=t[1]=t[2]=0;
    getdeep(u,0);
    return t[0]*t[0]+2*t[1]*t[2];
}
void solve(int u)
{
    ans+=calc(u,0);vis[u]=1;
    for (int e=head[u];e;e=a[e].next)
    {
        int v=a[e].to;if (vis[v]) continue;
        ans-=calc(v,a[e].w);
        sum=sz[v];root=0;
        getroot(v,0);
        solve(root);
    }
}
int main()
{
    n=gi();
    for (int i=1;i<n;i++)
    {
        int u=gi(),v=gi(),w=gi();
        a[++cnt]=(edge){v,head[u],w};head[u]=cnt;
        a[++cnt]=(edge){u,head[v],w};head[v]=cnt;
    }
    f[0]=sum=n;
    getroot(1,0);
    solve(root);
    int zsy=gcd(ans,n*n);
    printf("%d/%d\n",ans/zsy,n*n/zsy);
    return 0;
}

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

点分治总结

POJ 1741 树分治(点分治模板题)

CDQ分治总结

分治寻找最近点对

二叉树 + 递归 + 分治法总结

[模板] 点分治