题解 P4178 【Tree】
点分治模板题,大部分和模板1是一样的。
然后我天真地把我原来的代码的答案数组求了个前缀和就交了上去。。。T得不要不要的。。。
il void getroot(re int u,re int fa)
{
sz[u]=1;f[u]=0;
for(re int i=h[u];i;i=e[i].next)
{
re int v=e[i].to;
if(v==fa||vis[v]) continue;
getroot(v,u);//
sz[u]+=sz[v];
f[u]=max(f[u],sz[v]);//f[u]存该点以下最大子树
}
f[u]=max(f[u],sum-sz[u]);//肯定儿子要比爸爸多啊
if(f[u]<f[root]) root=u;
}
il void calc(re int u,re int dd,re int add)
{
cnt=0;d[u]=dd;
getdeep(u,0);
fp(i,1,cnt)
fp(j,1,cnt)
ans[o[i]+o[j]]+=add;//时间复杂度过高
}
il void work(re int u)
{
calc(u,0,1);vis[u]=1;
for(re int i=h[u];i;i=e[i].next)
{
re int v=e[i].to;
if(vis[v]) continue;
calc(v,e[i].w,-1);
sum=sz[v];root=0;
getroot(v,0);//
work(root);
}
}
认真分析题目,发现我们在求距离大于k的点对数上花费了大量的时间,并且点对计数用二层循环时间复杂度太高。
所以我们要在优化点对计数数过程下手。
对于根节点进行一次dfs,求出deep,并将其从小到大排序。(longn预处理省一层循环)
避免重复,只需要求出x<y的前提下deep[x]+deep[y]≤k的个数。
用i表示左指针,j表示右指针,i从左向右遍历,j从右向左。
如果deep[i]+deep[j]≤k,则点对(i,t)(i<t≤j)都符合题意,将j-i加入答案中,并且i++;否则j--。
然而这样还会重复计算在同一棵子树中的点对,所以再进行下一步dfs之前需要减去重复部分。
这样就能保证时间复杂度在O(nlog2n)以下。(而不是原来的O(n2long2n))
il void getroot(re int u,re int fa)
{
sz[u]=1;f[u]=0;
for(re int i=h[u];i;i=e[i].next)
{
re int v=e[i].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;
}
il int calc(re int u)
{
cnt=0;getdeep(u,0);sort(o+1,o+cnt+1);
re int i=1,j=cnt,sum=0;
while(i<j)
if(o[i]+o[j]<=k) sum+=j-i,i++;
else j--;//优化后的点对计数过程
return sum;
}
il void work(re int u)
{
d[u]=0;vis[u]=1;ans+=calc(u);
for(re int i=h[u];i;i=e[i].next)
{
re int v=e[i].to;
if(vis[v]) continue;
d[v]=e[i].w;
ans-=calc(v);//去重
sum=sz[v];root=0;
getroot(v,0);//重选重心
work(root);
}
}