【SCC】Proving Equivalences UVALive - 4287

传送门:https://vjudge.net/problem/UVALive-4287

分析

强连通分量(SCC) + 有向无环图(DAG) 的性质
由 SCC 性质知,一个 CC 内的点可以相互到达(也就是里面的定理已经可以相互证明了),所以我们使用 SCC 将问题转化为给定一个 DAG,求使 DAG 变成一个 CC 的最小连边数

下面我们专注于讨论下面的 Q
Q:给定一个 DAG,求使 DAG 变成一个 CC(连通分量) 的最小连边数

答案是多少呢?先给出结论:\(\max(cntS, cntT)\) (出度为 \(0\) 的点数与入度为 \(0\) 的点数的最大值)。

为什么呢?
我们以 \(cntT\) 较大的情况为例,\(cntS\) 较大的情况可类似得到。

当 \(T\) 较多时,每一个 \(T\) 点都必须要向着 \(S\) 点连边,具体方案构造新图(CC)的方案是先让每个 \(S\) 被不同的 \(T\) 连入,然后剩余的 \(T\) 随意连 \(S\) 。这样的构造方案一定能让新图中的任意两点相互到达:因为一个点 \(u\) 要到另一个点 \(v\),我们一定能让 \(u\) 先到一个 \(T\) 然后折返回一个通向 \(v\) 的 \(S\)。

这样的方案显然是最优的,因为你至少要让所有 \(T\) 都向 \(S\) 连一条边。

#pragma GCC optimize("O3")
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define debug(x) cerr << #x << ": " << x << endl
#define pb(a) push_back(a)
#define set0(a) memset(a,0,sizeof(a))
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dwn(i,a,b) for(int i=(a);i>=(b);i--)
#define ceil(a,b) (a+(b-1))/b
#define INF 0x3f3f3f3f
#define ll_INF 0x7f7f7f7f7f7f7f7f
typedef long long ll;
typedef pair<int,int> PII;
typedef pair<double,double> PDD;

inline void read(int &x) {
    int s=0;x=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')x=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    x*=s;
}

const int N=2e4+5, M=1e5+5;

int n, m;

struct node{
	int to, next;	
}e[M];

int h[N], tot;

void add(int u, int v){
	e[tot].to=v, e[tot].next=h[u], h[u]=tot++;
}

int ts, dfn[N], low[N];
int stk[N], top;
int id[N], cnt;
bool ins[N];
void tarjan(int u){
	dfn[u]=low[u]=++ts;
	stk[++top]=u, ins[u]=true;
	for(int i=h[u]; ~i; i=e[i].next){
		int go=e[i].to;
		if(!dfn[go]){
			tarjan(go);
			low[u]=min(low[u], low[go]);
		}else if(ins[go]) low[u]=min(low[u], dfn[go]);
	}
	
	if(dfn[u]==low[u]){
		int y;
		cnt++;
		do{
			y=stk[top--], ins[y]=false, id[y]=cnt; 
		}while(y!=u);
	}
}

int din[N], dout[N];

int main(){
	int T; read(T);
	while(T--){
		memset(h, -1, sizeof h);
		set0(dfn); set0(low);
		ts=tot=top=cnt=0;
		
		set0(din), set0(dout);
		
		cin>>n>>m;
		while(m--){
			int u, v; read(u), read(v);
			add(u, v);
		}
		
		rep(i,1,n) if(!dfn[i]) tarjan(i); // get the DAG

		rep(i,1,n) for(int j=h[i]; ~j; j=e[j].next){
			int go=e[j].to;
			if(id[i]!=id[go]) dout[id[i]]++, din[id[go]]++;
		}
		
		int cntS=0, cntT=0;
		rep(i,1,cnt)
			if(!din[i] && !dout[i]) cntS++, cntT++;
			else if(!din[i]) cntS++;
			else if(!dout[i]) cntT++;
		
		cout<<(cnt==1? 0: max(cntS, cntT))<<endl;
	}
    return 0;
}
上一篇:【LeetCode每日一题】【2021/12/3】1005. K 次取反后最大化的数组和


下一篇:一个案例展示opencv在医学图像病灶分割任务的常用操作