【学习笔记】点分治

点分治

模板

下文若未特殊指明,路径均指不重复经过某一条边的路径。

点分治,顾名思义,是在树上的分治。

设想我们要找是否存在长度为\(k\)的路径,该怎么办呢?

首先,我们将路径分类,分别为经过点\(root\)的路径,和在点\(root\)子树下的路径。

显然,对于第二种情况可以转化为第一种情况。

对于第一种情况,我们把每个点到\(root​\)的距离预处理出来,再用二分等方法求出方案数。就可以完美通过。

等等,真的完美吗?

我们发现,这里面经过\(root\)的路径可能经过重复的边。这是不合法的。不过,这种情况只会出在同一个子树下。

所以,在统计路径时,当处理到\(root\)的某个子树时,要先减掉这种路径。这样才能保证所有计算的路径合法。这样的程序才正确的。

然而,这样做其实会被出题人卡,当发生链的情况时,时间复杂度就会退化。

该如何解决此问题?

其实,我们可以证明,当选择树的重心时,时间复杂度最优。时间复杂度\(O(nlog^2n)​\)

#include<bits/stdc++.h>
using namespace std;
int cc,to[30000],net[30000],fr[30000],len[30000];
int q[30000],jl[30000],siz[30000],fa[30000];bool vis[30000]={0};
int maxr,n,m,root,h,t,k,ans,a,b,c;
void addedge(int u,int v,int c)
{
    cc++;
    to[cc]=v;net[cc]=fr[u];fr[u]=cc;len[cc]=c;
}
bool cmp(int x,int y)
{
    return jl[x]<jl[y];
}
void findroot(int x)
{
    int maxx=0;
    siz[x]=1;
    for (int i=fr[x];i;i=net[i])
    {
        if (to[i]==fa[x]||vis[to[i]]) continue;
        fa[to[i]]=x;findroot(to[i]);
        siz[x]+=siz[to[i]];
        maxx=max(maxx,siz[to[i]]);
    }
    maxx=max(maxx,n-siz[x]);
    if (maxx<maxr) maxr=maxx,root=x;
}
void dfs(int x)
{
    q[++t]=x;
    for (int i=fr[x];i;i=net[i])
    {
        if (to[i]==fa[x]||vis[to[i]]) continue;
        jl[to[i]]=jl[x]+len[i];
        fa[to[i]]=x;dfs(to[i]);
    }
}
int calc(int l,int r,int z)
{
    int sum=0;
    sort(q+l,q+r+1,cmp);
    for (int i=l,j=r + 1 ;i<=r;i++)
    {
        if (jl[q[i]]==k&&z){sum++;continue;}
        while ((jl[q[i]]+jl[q[j]]>k&&i<j) ||(j==r+1)) j--;
        if (jl[q[i]]+jl[q[j]]!=k) continue;
        int kk=j;
        while (jl[q[kk]]==jl[q[j]]&&i<kk) kk--;
        sum+=j - kk ; 
    }
    return sum;
}
void work(int x)
{
    maxr=root=2147483647;
    findroot(x);
    x=root;
    h=1;t=0;
    for (int i=fr[x];i;i=net[i])
    {
        jl[to[i]]=len[i];fa[to[i]]=x;
        if (vis[to[i]]) continue;
        dfs(to[i]);
        ans-=calc(h,t,0);
        h=t+1;
    }
    ans+=calc(1,t,1);
    vis[x]=true;
    for (int i=fr[x];i;i=net[i])
    {
        if (vis[to[i]]) continue;
        work(to[i]);
    }
}
int main()
{
    cin>>n>>m;
    for (int i=1;i<n;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        addedge(a,b,c);
        addedge(b,a,c);
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&k);ans=0;
        for (int j=1;j<=n;j++)
          vis[j]=false,fa[j]=0,jl[j]=0;
        work(1);
        if (ans>0) cout<<"AYE\n";else cout<<"NAY\n";
    }
    return 0;
}

课后练习

CF161D

聪聪可可

posted @ 2019-03-01 22:44  fmj_123  阅读(166)  评论(0编辑  收藏  举报