zoukankan      html  css  js  c++  java
  • 【洛谷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;
    }
    
  • 相关阅读:
    node.js开发 打包管理工具webpack
    node.js开发 npm包管理工具 npm 和 cnpm区别
    node.js开发 npm包管理工具
    node.js开发 1-概述
    脚手架-1概念
    前端开发 vue,angular,react框架对比2
    AttachDispatch
    画图软件orign的使用
    建立xml文件时遇到的编码问题和解决方法
    securecrt简介
  • 原文地址:https://www.cnblogs.com/stoorz/p/15542729.html
Copyright © 2011-2022 走看看