CF 703 F. Pairs of Paths

给出一棵树,询问m条路径,有多少对路径相交于一点。

 

路径相交只有两种情况,一种是相同lca,一种是不同lca。

先对路径[a,b]预处理,记录lca(a,b)下a,b分别在哪个儿子。

对于相同lca的:

 当前查询路径[a,b],则和该路径只在lca相交的路径数等于该lca(a,b)的路径总个数-在a下儿子的个数-在b下儿子的个数+ab下儿子的个数(容斥)。

这里注意的是,如果先统计所有儿子的个数,那么对于单点的路径,他会自己和自己匹配,最后需要减掉。最后结果就是总和/2.

如果是边处理边统计,则不会出现自己和自己匹配,最后结果也不需要除2.

 

对于不同lca的,那么相交必定是一条路径经过另外一条路径的lca。那么对于一个路径[a,b],那么就是统计其他路径的lca在lca(a,b)之上,而且其中一个端点在lca(a,b)的子树中且不在a,b的子树中。

 

因此由于需要统计比lca(a,b)小的路径,这里就有了偏序。对lca根据深度排序。对于lca(a,b),深度比它小的路径已经处理完了。那么和就是在lca(a,b)的子树的节点-在a子树的节点-在b子树的节点。

统计个数可以直接用树状数组。统计子树可以利用dfs序的进和出。求a子树的个数就是sum(out[a]) - sum(in[a]-1) (包括a自己)。

 

 


vector<int> g[N];
int dep[N],fa[N][20],dfn[N],in[N],out[N];
int la[N];
int a[N],b[N],pa[N],pb[N];
int idx;

void dfs(int t, int f){
	dep[t] = dep[f]+1;
	in[t] = idx;
	dfn[idx++]=t;
	fa[t][0] = f;
	for(int i = 1; i <20; ++i){
		fa[t][i] = fa[fa[t][i-1]][i-1];
	}
	for(int u : g[t]){
		if(u==f)continue;
		dfs(u,t);
	}
	out[t] = idx-1;
}

int lca(int a, int b){
	if(dep[a]<dep[b])swap(a,b);
	for(int i = 19;i>=0;--i){
		if(dep[fa[a][i]]>=dep[b]) a = fa[a][i];
	}
	if(a==b) return a;
	for(int i = 19; i >=0;--i){
		if(fa[a][i] != fa[b][i]) {
			a = fa[a][i];
			b = fa[b][i];
		}
	}
	return fa[a][0];
}

int sum[N];
void add(int v){
	for(; v <=idx; v += v&-v){
		sum[v]++;
	}
}

int get1(int v){
	int ret = 0;
	while(v>0){
		ret += sum[v];
		v-=v&-v;
	}
	return ret;
}

int get(int v){
	if(v==-1) return 0;
	return get1(out[v]) - get1(in[v]-1);
}

int get_a(int f, int v){
	if(v==f)return -1;
	for(int i = 19;i >=0;--i){
		if(dep[fa[v][i]] >=dep[f]+1){
			v = fa[v][i];
		}
	}
	return v;
}

int main(){
	int n,m;
	cin>>n;
	fr(i,0,n-1){
		int u,v;
		sf("%d%d",&u,&v);
		g[u].pb(v);
		g[v].pb(u);
	}

	idx = 1;
	dfs(1,1);

	cin>>m;
	vector<int> pos;
	fr(i,0,m){
		sf("%d%d",&a[i],&b[i]);
		if(dep[a[i]] > dep[b[i]]){
			swap(a[i],b[i]);
		}
		else if(dep[a[i]] == dep[b[i]]){
			if(in[a[i]] > in[b[i]]){
				swap(a[i],b[i]);
			}
		}
		la[i] = lca(a[i],b[i]);
		pa[i] = get_a(la[i],a[i]);	
		pb[i] = get_a(la[i],b[i]);	
		pos.pb(i);
	}

	auto cmp = [&](int i, int j){
		return dep[la[i]]<dep[la[j]];
	};
	sort(pos.begin(), pos.end(), cmp);

	int last = 0;
	ll ans = 0;
	for(int i = 0; i < pos.size();++i){
		while(last<pos.size() && dep[la[pos[last]]] < dep[la[pos[i]]]){
			int idx = pos[last++];
			add(in[a[idx]]);
			add(in[b[idx]]);
		}
		int idx = pos[i];
		int tot = get(la[idx]);
		int ask = get(pa[idx]);
		ask += get(pb[idx]);
		ans += tot - ask;
	}

	map<int, vector<pair<int,int> >> p;
	for(int i = 0; i < pos.size();++i){
		int idx = pos[i];
		int pa = get_a(la[idx],a[idx]);	
		int pb = get_a(la[idx],b[idx]);	
		if(pa>pb)swap(pa,pb);
		p[la[idx]].pb(mp(pa,pb));
	}

	ll ans2 = 0;
	for(auto it : p){
		map<int,int> ft;
		map<pair<int,int>,int > ftt;
		auto&vec = it.second;
		ll tot = 0;
		for(auto x : vec) {
			ans2 += tot - ft[x.first] - ft[x.second] + ftt[x];
			if(x.first>=0)
				ft[x.first]++;
			if(x.second>=0)
				ft[x.second]++;
			if(x.first>=0&&x.second>=0)
				ftt[x]++;
			tot++;
		}
	}

	cout<<ans+ans2<<endl;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值