【洛谷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;
}
posted @ 2021-11-12 01:55  stoorz  阅读(21)  评论(0编辑  收藏  举报