【洛谷P5327】语言
题目
题目链接:https://www.luogu.com.cn/problem/P5327
九条可怜是一个喜欢规律的女孩子。按照规律,第二题应该是一道和数据结构有关的题。
在一个遥远的国度,有 \(n\) 个城市。城市之间有 \(n − 1\) 条双向道路,这些道路保证了任何两个城市之间都能直接或者间接地到达。
在上古时代,这 \(n\) 个城市之间处于战争状态。在高度闭塞的环境中,每个城市都发展出了自己的语言。而在王国统一之后,语言不通给王国的发展带来了极大的阻碍。为了改善这种情况,国王下令设计了 \(m\) 种通用语,并进行了 \(m\) 次语言统一工作。在第 \(i\) 次统一工作中,一名大臣从城市 \(s_i\) 出发,沿着最短的路径走到了 \(t_i\),教会了沿途所有城市(包括 \(s_i, t_i\))使用第 \(i\) 个通用语。
一旦有了共通的语言,那么城市之间就可以开展贸易活动了。两个城市 \(u_i, v_i\) 之间可以开展贸易活动当且仅当存在一种通用语 \(L\) 满足 \(u_i\) 到 \(v_i\) 最短路上的所有城市(包括 \(u_i, v_i\)),都会使用 \(L\)。
为了衡量语言统一工作的效果,国王想让你计算有多少对城市 \((u, v)\ (u < v)\),他们之间可以开展贸易活动。
\(n,m\leq 10^5\)。
思路
好优美的数据结构题。
直接结算的话肯定会算重。需要找一些性质。
考虑对于任意一个点 \(x\),求出有多少个点能与他产生贡献。很显然,这些点一定会形成一个子连通块。而我们需要计算的就是这个子连通块的大小。
如果一条路径 \((s_i,t_i)\) 包含了点 \(x\),那么 \(s_i,t_i\) 之间的所有点都在这个连通块内。所以这个连通块的大小就是所有包含 \(x\) 的路径两端所有点的最小生成树的大小。
如果已经知道了若干个点,需要求出他们最小生成树的大小,可以按照 dfs 序排序后,每一个点的深度之和减去相邻的点 lca 的深度之和。
按照 dfs 序为下标建立一棵线段树,对于点 \(x\),把所有包含 \(x\) 的路径的 \(s\) 和 \(t\) 在线段树上打上标记,然后线段树区间合并的时候可以维护这个区间的答案。
最后点 \(x\) 的贡献就是线段树根节点的答案,再减去 dfs 序最小最大两个点的 lca 的深度。
显然不可能对于每个点都暴力遍历一次所有包含这个点的路径。把一条路径 \((s,t)\) 拆成两条链,在 \(s,t\) 打上 \(+1\) 的标记,\(\text{lca}(s,t),\text{fa}[\text{lca}(s,t)]\) 处打上 \(-1\) 的标记,然后动态开点线段树合并即可。
由于每次线段树区间合并都要求一次 lca,时间复杂度是 \(O(n\log^2 n)\)。采用 \(O(n\log n)-O(1)\) lca 可以做到 \(O(n\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,LG=18;
int n,m,tot,id[N],rk[N],dep[N],head[N],rt[N],f[N][LG+1];
ll ans;
vector<int> a[N];
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
void dfs1(int x,int fat)
{
f[x][0]=fat; dep[x]=dep[fat]+1;
id[x]=++tot; rk[tot]=x;
for (int i=1;i<=LG;i++)
f[x][i]=f[f[x][i-1]][i-1];
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fat) dfs1(v,x);
}
}
int lca(int x,int y)
{
if (!x || !y) return 0;
if (dep[x]<dep[y]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[f[x][i]]>=dep[y]) x=f[x][i];
if (x==y) return x;
for (int i=LG;i>=0;i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
struct SegTree
{
int tot,lc[N<<5],rc[N<<5],cnt[N<<5],lp[N<<5],rp[N<<5];
ll ans[N<<5];
void pushup(int x)
{
lp[x]=lp[lc[x]] ? lp[lc[x]] : lp[rc[x]];
rp[x]=rp[rc[x]] ? rp[rc[x]] : rp[lc[x]];
ans[x]=ans[lc[x]]+ans[rc[x]]-dep[lca(rp[lc[x]],lp[rc[x]])];
}
void updleaf(int x,int k)
{
if (cnt[x]) lp[x]=rp[x]=rk[k],ans[x]=dep[rk[k]];
if (!cnt[x]) lp[x]=rp[x]=ans[x]=0;
}
int merge(int x,int y,int l,int r)
{
if (!x || !y) return x|y;
if (l==r) { cnt[x]+=cnt[y]; updleaf(x,l); return x; }
int mid=(l+r)>>1;
lc[x]=merge(lc[x],lc[y],l,mid);
rc[x]=merge(rc[x],rc[y],mid+1,r);
pushup(x);
return x;
}
int update(int x,int l,int r,int k,int v)
{
if (!x) x=++tot;
if (l==r) { cnt[x]+=v; updleaf(x,l); return x; }
int mid=(l+r)>>1;
if (k<=mid) lc[x]=update(lc[x],l,mid,k,v);
if (k>mid) rc[x]=update(rc[x],mid+1,r,k,v);
pushup(x);
return x;
}
}seg;
void dfs2(int x)
{
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=f[x][0])
{
dfs2(v);
rt[x]=seg.merge(rt[x],rt[v],1,n);
}
}
for (int i=0;i<(int)a[x].size();i++)
{
int y=abs(a[x][i]),z=(a[x][i]>0)?1:-1;
rt[x]=seg.update(rt[x],1,n,id[y],z);
}
ans+=seg.ans[rt[x]]-dep[lca(seg.lp[rt[x]],seg.rp[rt[x]])];
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
tot=0; dfs1(1,0);
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
int p=lca(x,y);
a[x].push_back(x); a[x].push_back(y);
a[y].push_back(x); a[y].push_back(y);
a[p].push_back(-x); a[p].push_back(-y);
a[f[p][0]].push_back(-x); a[f[p][0]].push_back(-y);
}
dfs2(1);
printf("%lld",ans/2LL);
return 0;
}