Adam Gąsienica‑Samek Contest 1-I、竞赛图、倍增

发布时间 2023-10-06 01:19:36作者: yoshinow2001

Adam Gąsienica‑Samek Contest 1-I、竞赛图、倍增

题面:https://codeforces.com/gym/104479/problem/I

题意:

  • 有一张 \(n\) 个点的竞赛图,图未给出,但是对每个点 \(i\) ,知道一个 \(c_i\) 表示从 \(i\) 出发能到达几个点。

  • \(m\) 个可选的操作,每个操作形如 \((u_i,v_i)\) ,表示把 \((u_i,v_i)\) 之间的有向边变成无向边。

  • \(q\) 次询问,每次问最少需要几次操作,才能使得 \(a\) 能够到达 \(b\)

  • 因为图的形态不确定,所以还会问最好/最坏情况下的最少操作是多少次。

\(1\leq n,m,q\leq 5\times 10^5\)


题解:

写一些性质

竞赛图有一些性质:

  • 1、竞赛图是强连通的,当且仅当存在哈密顿回路。

  • 2、竞赛图一定存在哈密顿通路(归纳证明)。

  • 3、如果存在环,一定存在三元环(很显然)。

  • 4、竞赛图的强连通分量是一条链加上若干条边,类似这种:

dp、倍增

因此缩点完后,每个点所在的强连通分支是可以通过 \(c_i\) 来直接确定的,按照拓扑序从小到大排成一行,拓扑序小的自然可以到拓扑序大的。

把每个操作 \((u,v)\) 记在拓扑序大的点所在的连通分支上,对每个强连通分支,去dp,记 \(f[i][j]\) 表示从连通分支 \(i\) 出发,做 \(2^j\) 次操作所能到达的最左边(拓扑序最小)的连通分支是多少。

#include<bits/stdc++.h>
#define endl '\n'
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
const int N=5e5+5;
int n,m,q;
int bl[N],f[N][25];
pair<int,int> c[N];
int main(){
	fastio;
	cin>>n>>m>>q;
	rep(i,1,n){
		cin>>c[i].first;
		c[i].second=i;
		f[i][0]=i;
	}
	sort(c+1,c+n+1);
	int lst=-1,tot=0;
	for(int i=n;i>=1;i--){
		if(c[i].first!=lst){
			lst=c[i].first;
			tot++;
		}
		bl[c[i].second]=tot;
	}
	rep(i,1,m){
		int u,v;
		cin>>u>>v;
		u=bl[u];v=bl[v];
		if(u==v)continue;
		if(u<v)swap(u,v);
		f[u][0]=min(f[u][0],v);
	}
	for(int i=tot-1;i>=1;i--)f[i][0]=min(f[i][0],f[i+1][0]);
	rep(j,1,20){
		rep(i,1,tot)f[i][j]=f[f[i][j-1]][j-1];
		for(int i=tot-1;i>=1;i--)f[i][j]=min(f[i][j],f[i+1][j]);
	}
	rep(i,1,q){
		char ch;
		int a,b,ans=0;
		cin>>ch>>a>>b;
		a=bl[a];b=bl[b];
		if(a>b){
			for(int j=20;j>=0;j--){
				if(f[a][j]>b){
					ans|=(1<<j);
					a=f[a][j];
				}
			}
			if(f[a][0]<=b)ans++;
			else ans=-1;
		}
		cout<<ans<<endl;
	}
	return 0;
}