【XSY4746】会议选址(三度化,边分治,点分治)
这种关键点的重心问题,一般有两种思路。
一种是合并:对于两个不交的点集 \(S,T\),\(S\cup T\) 的重心必在 \(S,T\) 重心间的路径上,二分即可,需要数据结构支持 dfn 区间内 \(S\cup T\) 内的点的个数。
使用该做法能将本题做到 \(O(n\log^3n+q\log n)\)。
另一种想法是,对于单次询问点集 \(S\),先从任意一个点开始,考虑往当前最大的子树走,直到最大的子树大小小于等于 \(S/2\)。
如果用树剖+二分暴躁加速这个过程的话,单次询问还是 \(O(\log^3n)\) 的。
优秀的做法是使用三度化和边分治。我们先一一初步介绍。
三度化的具体作用是:将带权树,在不改变任意两点间距离、任意两点间祖先后代关系的情况下,转化成每个点度数至多为 \(3\) 的点数至多翻倍的树.
但三度化会改变两点间的直接父子关系,以及新增一些原树没有的虚点,这些都是需要注意的。
三度化有两种方法,如下图:
称原树中的点为旧点,新增的点为虚点,三度化后的树为新树。
其中使用第一种方法,能让新树的深度不超过 \(O(d\log \frac{n}{d})\),其中 \(d,n\) 是原树的深度和大小。但这个性质和本题没有关系。
边分治是如下的过程:
假设当前树大小为 \(n\),每个点的度数不超过 \(D\)。找到一条边使得分出来两棵树的大小相差最小,设找到的边是 \((u,v)\),且 \(u\) 对应的那棵子树的大小更大,设为 \(S\)。
设 \(u\) 的各个儿子的子树大小为 \(s_1,\cdots,s_{D-1}\)(不足 \(D-1\) 个儿子用 \(0\) 补齐),那么显然对于任意 \(i\) 满足 \(s_i\leq n-s_i\),进一步地,应满足 \((n-s_i)-s_i\geq S-(n-S)\),得到 \(S+s_i\leq n\)。
将这 \(D-1\) 条不等式加起来,得到 \((D-1)S+\sum s_i\leq (D-1)n\),即 \(DS-1\leq (D-1)n\),那么 \(S\leq\frac{D-1}{D}n+1\)。
从而,在将树三度化的基础上,按上述方法找到的边,能使得分成的两棵子树大小都不超过 \(\frac{2}{3}n+1\)。
于是边分树的深度是 \(O(\log_{\frac{3}{2}}n)=O\left(\dfrac{\log n}{\log\frac{3}{2}}\right)=O(\log n)\) 级别的。
具体回到这题。我们考虑用如下的方式找原树上若干关键点的重心:对于每一条边 \((a,b)\),若 \(a\) 子树内的关键点个数小于 \(b\) 子树内的关键点个数,那么令这条边的方向是 \(a\to b\);否则,若 \(b\) 子树内的关键点个数小于 \(a\) 子树内的关键点个数,那么令这条边的方向是 \(b\to a\)。若 \(a,b\) 子树内的关键点个数相同,那么令这条边无向。
那么可以证明,最后树上肯定形如:存在一个无向边的连通块,然后其余所有边都指向这个连通块。此时连通块内的任意一个点都可以是这些关键点的重心。
考虑将这个方法作用在新树上:那么原树上一条边 \((fa_a,a)\) 的指向,和新树上 \((fa_a',a)\) 的指向相同。
不妨假设新树上那些关键点的重心在 \(rt\),注意 \(rt\) 可能是虚点。设 \(rt\) 在旧点 \(u\) 在新树上的子树内,但不在 \(u\) 任意一个旧儿子在新树上的子树内。考虑 \((fa_u,u)\) 的指向,由于 \(fa_u'\to u\),那么 \(fa_u\to u\)。对于 \(u\) 的任意旧儿子 \(v\),考虑 \((u,v)\) 的指向,由于 \(v\to fa_v'\),那么 \(v\to u\)。从而,我们证明了 \(u\) 是原树上关键点的重心。
于是我们找出新树上的重心即可。我们从边分树的根开始往下走。每次考虑当前边 \(e\) 的指向。考虑直接数出 \(e\) 两边的关键点个数。首先要数 \(A,B\) 子树内编号在 \([l,r]\) 内的点数,可以通过 dfn 序+可持久化做到 \(O(n\log n)-O(\log n)\)。而对于 \(A,B\) 外的那些点,它们在边分树上对应不超过 \(O(\log n)\) 棵子树,且这些子树内的关键点个数我们也可以在走下来的过程中顺便记录,于是我们只需要知道当前这些子树每一棵属于 \(A\) 还是 \(B\) 即可:这其实很简单,因为这些子树肯定整体都在 \(e\) 的一边,所以随便选取一个代表点,利用 dfn 序判断它在 \(e\) 的哪一边即可。
于是,我们单次可以 \(O(\log^2n)\) 找到一个区间的关键点的重心。
再离线下来用点分治求出在原树上对应的重心到所有点的距离,可以做到 \(O((n+q)\log n)\)。
从而,我们在 \(O(n\log n+q\log^2n)\) 的时间内解决这个问题。
#include<bits/stdc++.h>
#define N 100010
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n,Q;
vector<int> e[N];
namespace P2
{
struct query{int u,id,coef;};
vector<query> q[N];
}
namespace P1
{
#define lc(u) ch[u][0]
#define rc(u) ch[u][1]
const int N1=N<<1,N2=N1<<1;
int n1,cnt=1,head[N1],to[N1<<1],nxt[N1<<1];
int dfx,fa[N1],in[N1],out[N1],belong[N1];
void adde(int u,int v)
{
to[++cnt]=v,nxt[cnt]=head[u],head[u]=cnt;
to[++cnt]=u,nxt[cnt]=head[v],head[v]=cnt;
}
void D3(int u,int fa)
{
vector<int> son;
for(int v:e[u]) if(v!=fa) son.push_back(v);
function<int(int,int)> dfs=[&](int l,int r)
{
if(l==r) return son[l];
int mid=(l+r)>>1;
int x=(l==0&&r==(int)son.size()-1?u:++n1);
belong[x]=u;
adde(x,dfs(l,mid)),adde(x,dfs(mid+1,r));
return x;
};
belong[u]=u;
if(!son.empty())
{
if(son.size()==1) adde(u,son[0]);
else dfs(0,son.size()-1);
}
for(int v:son) D3(v,u);
}
void dfs(int u)
{
in[u]=++dfx;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa[u]) continue;
fa[v]=u,dfs(v);
}
out[u]=dfx;
}
int n2,Rt,ch[N2][2],eid[N2];
int idx,id[N1],L[N2],R[N2];
int nrt,maxn,nn,size[N1];
bool vis[N1<<1];
void getsize(int u,int fa)
{
size[u]=1;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa||vis[i]) continue;
getsize(v,u),size[u]+=size[v];
}
}
void getroot(int u,int fa)
{
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa||vis[i]) continue;
int x=abs(nn-size[v]-size[v]);
if(x<maxn) maxn=x,nrt=i;
getroot(v,u);
}
}
int build(int u)
{
getsize(u,0);
if(size[u]==1)
{
id[u]=L[u]=R[u]=++idx;
return u;
}
int x=++n2;
nn=size[u],maxn=INT_MAX,getroot(u,0);
eid[x]=nrt,vis[nrt]=vis[nrt^1]=1;
int a=to[nrt],b=to[nrt^1];
lc(x)=build(a),rc(x)=build(b);
L[x]=L[lc(x)],R[x]=R[rc(x)];
return x;
}
int rt[N];
namespace Seg
{
const int NN=10000000;
int node,ch[NN][2],sum[NN];
void update(int &u,int lst,int l,int r,int x)
{
u=++node,lc(u)=lc(lst),rc(u)=rc(lst),sum[u]=sum[lst]+1;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) update(lc(u),lc(lst),l,mid,x);
else update(rc(u),rc(lst),mid+1,r,x);
}
int query(int u,int l,int r,int ql,int qr)
{
if(!u) return 0;
if(ql<=l&&r<=qr) return sum[u];
int mid=(l+r)>>1,ans=0;
if(ql<=mid) ans+=query(lc(u),l,mid,ql,qr);
if(qr>mid) ans+=query(rc(u),mid+1,r,ql,qr);
return ans;
}
int query(int pl,int pr,int ql,int qr)
{
return query(rt[pr],1,n1,ql,qr)-query(rt[pl-1],1,n1,ql,qr);
}
}
struct data{int kp,s;};
bool judge(int p,int a,int b)
{
if(fa[a]==b) return in[a]<=in[p]&&in[p]<=out[a];
return !(in[b]<=in[p]&&in[p]<=out[b]);
}
int query(int u,int pl,int pr,vector<data> c)
{
if(u<=n1) return u;
int a=to[eid[u]],b=to[eid[u]^1];
int sa=Seg::query(pl,pr,L[lc(u)],R[lc(u)]),ta=0;
int sb=Seg::query(pl,pr,L[rc(u)],R[rc(u)]),tb=0;
for(auto now:c) (judge(now.kp,a,b)?ta:tb)+=now.s;
if(sa+ta>sb+tb)
{
c.push_back(data{b,sb});
return query(lc(u),pl,pr,c);
}
else
{
c.push_back(data{a,sa});
return query(rc(u),pl,pr,c);
}
}
void main()
{
n1=n,D3(1,0); dfs(1);
n2=n1,Rt=build(1);
for(int i=1;i<=n;i++)
Seg::update(rt[i],rt[i-1],1,n1,id[i]);
for(int i=1;i<=Q;i++)
{
int l=read(),r=read();
int u=belong[query(Rt,l,r,vector<data>())];
P2::q[r].push_back(P2::query{u,i,1});
P2::q[l-1].push_back(P2::query{u,i,-1});
}
}
#undef lc
#undef rc
}
namespace P2
{
int fa[N];
int rt,maxn,nn,size[N];
bool vis[N];
vector<int> dis[N];
void getsize(int u,int fa)
{
size[u]=1;
for(int v:e[u]) if(v!=fa&&!vis[v])
getsize(v,u),size[u]+=size[v];
}
void getroot(int u,int fa)
{
int nmax=nn-size[u];
for(int v:e[u]) if(v!=fa&&!vis[v])
getroot(v,u),nmax=max(nmax,size[v]);
if(nmax<maxn) maxn=nmax,rt=u;
}
void getdis(int u,int fa,int dis,vector<int> *D)
{
D[u].push_back(dis);
for(int v:e[u]) if(v!=fa&&!vis[v])
getdis(v,u,dis+1,D);
}
void build(int u)
{
vis[u]=1;
getdis(u,0,0,dis);
for(int v:e[u])
{
if(vis[v]) continue;
getsize(v,0);
nn=size[v],maxn=INT_MAX,getroot(v,0);
fa[rt]=u,build(rt);
}
}
struct data
{
ll sd; int sz;
data(){}
data(ll a,int b){sd=a,sz=b;}
void operator += (data b){sd+=b.sd,sz+=b.sz;}
data operator - (data b){return data(sd-b.sd,sz-b.sz);}
ll calc(int d){return 1ll*d*sz+sd;}
}s1[N],s2[N];
ll ans[N];
void add(int x)
{
int u=x;
s1[u]+=data(0,1);
for(int i=(int)dis[x].size()-2;i>=0;i--,u=fa[u])
s1[fa[u]]+=data(dis[x][i],1),s2[u]+=data(dis[x][i],1);
}
ll query(int x)
{
int u=x;
ll ans=s1[u].sd;
for(int i=(int)dis[x].size()-2;i>=0;i--,u=fa[u])
ans+=(s1[fa[u]]-s2[u]).calc(dis[x][i]);
return ans;
}
void main()
{
getsize(1,0);
nn=size[1],maxn=INT_MAX,getroot(1,0);
build(rt);
for(int i=1;i<=n;i++)
{
add(i);
for(auto now:q[i]) ans[now.id]+=now.coef*query(now.u);
}
for(int i=1;i<=Q;i++)
printf("%lld\n",ans[i]);
}
}
int main()
{
n=read(),Q=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[u].push_back(v),e[v].push_back(u);
}
P1::main();
P2::main();
return 0;
}