点分治
下文若未特殊指明,路径均指不重复经过某一条边的路径。
点分治,顾名思义,是在树上的分治。
设想我们要找是否存在长度为(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
";else cout<<"NAY
";
}
return 0;
}
课后练习
CF161D
聪聪可可