点分治学习小记

点分治

通过一道题,发现了点分治这个算法,现在学习一下并做一个总结。

预备知识
  • 树的直径:树上最长的一条路径
    • 求法:两次DFS。
      • 第一次DFS随意定根,找到离根最远的点。
      • 第二次DFS以最远点为根,再找到离根最远的地方,路径长度就是树的直径。
    • 证明:脑补可得。
  • 树的重心:找到一个点,其所有子树中最大的子树节点最少。(这个点作为根时最大的子树节点个数最少)
    • 优点:删去重心之后,生成的多棵树尽可能平衡。
    • 代码
int sz[MAXN],rt,n,mx[MAXN];
void dfs(int x,int fa)
{
    sz[x]=1,mx[x]=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (v==fa) continue;
        dfs(v,x);
        sz[x]+=sz[v];
        mx[x]=max(mx[x],sz[v]);
    }
    mx[x]=max(mx[x],n-sz[x]);
    if (mx[x]<mx[rt] || (mx[x]==mx[rt] && x<rt))
        rt=x;
}

其中rt是树的重心,mx[rt]是树的重量。

点分治

点分治是在树上进行分治的一种方法:根据点的性质来进行“分”,再“治”。

如何“分”

分治的思想是为了减少计算的量级。
根据这一思想,我们选择树的重心为根,可以保证每次子树的大小不超过 2n 2 n ,递归深度就能保证递归深度保持在 log n l o g   n 级别,保证不发生退化,高效处理。

如何“治”

我们发现了一个问题:在计算当前子树的答案时,有一些“完全被子树的子树包含”,也就是答案与当前子树的根无关的贡献,在进行下一步递归时,会被计算到。
如何处理这个问题呢?运用容斥的思想,每次减去所有“仅与子树有关”的答案即可。

例1 POJ1741 Tree

我是题目链接

题目大意:一棵树有 n n 个节点,每条边有一个边权w[i],求两点之间距离不超过k的点对个数。
n104,w[i]1000 n ≤ 10 4 , w [ i ] ≤ 1000

思路:数据范围枚举是肯定过不去的。思考分治来降低枚举的量级。
按重心将树分成了若干子树后,dfs得到子树中每一个节点到当前根的距离,two-pointers统计答案。
发现有重复,观察到重复的情况就是上面提到的“仅与子树有关”的答案(可以理解为是从某一个点向上出发到根再原路折返回来造成的答案),用容斥的思想减去即可。

代码

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#include<cctype>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (!isdigit(c)) {if (c=='-') f=-1;c=getchar();}
    while (isdigit(c)) {sum=sum*10+c-'0';c=getchar();}
    return sum*f;
}
const int MAXN=10010;
struct edge
{
    int next,to,val;
};
edge e[MAXN*2];
int head[MAXN],cnt;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
bool visit[MAXN];
int sz[MAXN],rt;
int n,k,ans,dis[MAXN],mx[MAXN];
void get_root(int x,int fa)
{
    sz[x]=1,mx[x]=0;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (visit[v] || v==fa) continue;
        get_root(v,x);
        sz[x]+=sz[v];
        mx[x]=max(mx[x],sz[v]);
    }
    mx[x]=max(mx[x],n-sz[x]);
    if (mx[x]<mx[rt]) rt=x;
}
vector <int> tmp;
void get_dis(int x,int fa)
{
    tmp.push_back(dis[x]);
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (v==fa || visit[v]) continue;
        dis[v]=dis[x]+e[i].val;
        get_dis(v,x);
    }
} 
int calc(int x,int init)
{
    int tot=0;
    dis[x]=init;
    tmp.clear();
    get_dis(x,0);
    sort(tmp.begin(),tmp.end());
    int l=0,r=tmp.size()-1;
    while (l<r)
    {
        if (tmp[l]+tmp[r]<=k)
            tot+=r-l,l++;
        else
            r--;
    }
    return tot;
}
void dfs(int x)
{
    ans+=calc(x,0);
    visit[x]=1;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (visit[v]) continue;
        ans-=calc(v,e[i].val);
        n=sz[v];
        rt=0;
        get_root(v,0);
        dfs(rt);
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    while (n+k)
    {
        memset(head,0,sizeof(head));
        memset(visit,0,sizeof(visit));
        memset(dis,0,sizeof(dis));
        cnt=0;
        for (int i=1;i<n;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w),addedge(v,u,w);
        }
        mx[0]=INF;
        rt=0,ans=0;
        get_root(1,0);
        dfs(rt);
        printf("%d\n",ans);
        scanf("%d%d",&n,&k);    
    }
    return 0;
}

例2 BZOJ2152 聪聪可可

题目链接
题目大意:给定一棵树,求树上所有长度是3的倍数的路径条数。

思路:此题两种做法。
- 树形DP: f[i][j] f [ i ] [ j ] 表示 i i 的子树,模3意义下余j的节点个数,每次DFS统计子树答案,在根据边权做如下轮换,统计答案即可。

int tmp[3];
memcpy(tmp,f[v],sizeof(tmp));
f[v][0]=tmp[e[i].val%3];
f[v][1]=tmp[(e[i].val+1)%3];
f[v][2]=tmp[(e[i].val+2)%3];
  • 点分治:考虑树形DP统计答案“较为复杂”,采用点分治,分治时记录子树内部模3下的路径长度条数即可。

树形DP:

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#include<cctype>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (!isdigit(c)) {if (c=='-') f=-1;c=getchar();}
    while (isdigit(c)) {sum=sum*10+c-'0';c=getchar();}
    return sum*f;
}
const int MAXN=20010;
struct edge
{
    int next,to,val;
};
edge e[MAXN*2];
int head[MAXN],cnt;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
int f[MAXN][3],ans;
void dfs(int x,int fa)
{
    f[x][0]=1;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (v==fa) continue;
        dfs(v,x);
        int tmp[3];
        memcpy(tmp,f[v],sizeof(tmp));
        f[v][0]=tmp[e[i].val%3];
        f[v][1]=tmp[(e[i].val+1)%3];
        f[v][2]=tmp[(e[i].val+2)%3];
        ans+=f[x][0]*f[v][0];
        ans+=f[x][1]*f[v][2];
        ans+=f[x][2]*f[v][1];
        f[x][0]+=f[v][0];
        f[x][1]+=f[v][1];
        f[x][2]+=f[v][2];
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for (int i=1;i<n;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w),addedge(v,u,w);
    }
    dfs(1,0);
    ans=ans*2+n;
    int gcd=__gcd(ans,n*n);
    cout<<ans/gcd<<"/"<<n*n/gcd;
    return 0;
}

点分治

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#include<cctype>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back

using namespace std;

inline ll read()
{
    long long f=1,sum=0;
    char c=getchar();
    while (!isdigit(c)) {if (c=='-') f=-1;c=getchar();}
    while (isdigit(c)) {sum=sum*10+c-'0';c=getchar();}
    return sum*f;
}
const int MAXN=20010;
struct edge
{
    int next,to,val;
};
edge e[MAXN*2];
int head[MAXN],cnt;
void addedge(int u,int v,int w)
{
    e[++cnt].next=head[u];
    e[cnt].to=v;
    e[cnt].val=w;
    head[u]=cnt;
}
bool visit[MAXN];
int rt,mx[MAXN],sz[MAXN],n;
void get_root(int x,int fa)
{
    mx[x]=0,sz[x]=1;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (v==fa || visit[v])
            continue;
        get_root(v,x);
        sz[x]+=sz[v];
        mx[x]=max(mx[x],sz[v]);
    }
    mx[x]=max(mx[x],n-sz[x]);
    if (mx[x]<mx[rt]) rt=x;
}
int dis[MAXN],num[MAXN],ans;
void get_dis(int x,int fa)
{
    num[dis[x]%3]++;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (visit[v] || v==fa) continue;
        dis[v]=(dis[x]+e[i].val)%3;
        get_dis(v,x);
    } 
}
int calc(int x,int dist)
{
    num[0]=num[1]=num[2]=0;
    dis[x]=dist%3;
    get_dis(x,0);
    return num[0]*num[0]+num[1]*num[2]*2;
}
void dfs(int x)
{
    ans+=calc(x,0);
    visit[x]=1;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (visit[v]) continue;
        ans-=calc(v,e[i].val%3);
        rt=0,n=sz[v];
        get_root(v,0);
        dfs(rt);
    }
}
int main()
{
    scanf("%d",&n);
    int nn=n;
    for (int i=1;i<n;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        w%=3;
        addedge(u,v,w),addedge(v,u,w);
    }
    mx[0]=INF,rt=0;
    get_root(1,0);
    dfs(rt);
    int gcd=__gcd(ans,nn*nn);
    cout<<ans/gcd<<"/"<<nn*nn/gcd;
    return 0;
}
点分治的一般模式
  • 找到重心
  • 计算子树内部答案
  • 重置重心,向深层继续分治。

动态点分治

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值