AcWing 368. 银河 题解(强连通分量做差分约束问题)

11 篇文章 0 订阅
4 篇文章 0 订阅

AcWing 368. 银河
利用强连通分量求正环,在强连通分量内任意两点其实都是一个环,如果任意强连通分量内的任意两点的权值大于0,那么就说明这是一个正环,则无解
还需要注意的是:
差分约束的建图
N 颗恒星的亮度值总和至少有多大
求最小->求所有下界的最大->最长路 √
求最大->求所有上界的最小->最短路

最长路

dist[j] ≥ dist[t] + w[i] t+w[i]→j

T=1: A=B => A≥B B≥A B+0→A A+0→B
T=2: A<B => B≥A+1 A+1→B
T=3: A≥B => A≥B B+0→A
T=4: A>B => A≥B+1 B+1→A
T=5: A≤B => B≥A A+0→B

#include<bits/stdc++.h>

using namespace std;

typedef long long ll; 

const int N = 100010, M = 600010;

int h[N], hs[N], e[M], ne[M], w[M], idx;
int dist[N];
int low[N], dfn[N], timep, scc_cnt, id[N];
int Size[N];  //记录连通块个数 
int stk[N];
bool is_stk[N];
int top;
int n, m;
ll ans;

void add(int h[], int a, int b, int c){
	e[idx] = b;
	w[idx] = c;
	ne[idx] = h[a];
	h[a] = idx ++ ;
}

void tarjan(int u){  //找强连通分量 
	low[u] = dfn[u] = ++ timep;
	stk[ ++ top] = u;
	is_stk[u] = true;
	
	for(int i = h[u]; ~i; i = ne[i]){
		int j = e[i];
		if(!dfn[j]){
			tarjan(j);
			low[u] = min(low[u], low[j]);
		}
		else if(is_stk[j]){
			low[u] = min(low[u], dfn[j]);
		}
	}
	
	if(low[u] == dfn[u]){
		++ scc_cnt;
		int y;
		do{
			y = stk[top -- ];
			id[y] = scc_cnt;
			is_stk[y] = false;
			Size[scc_cnt] ++ ;
		}while(u != y);
	}
}

int main()
{
	cin>>n>>m;
	
	memset(h, -1, sizeof h);
	memset(hs, -1, sizeof hs);
	
	for(int i = 1; i <= n; i ++ ) add(h, 0, i, 1);  //超级源点 和i有一个1的边 
	
	while(m -- ){
		int t, a, b;
		scanf("%d%d%d", &t, &a, &b);
		//求最长路,小指向大 
		if(t == 1) add(h, a, b, 0), add(h, b, a, 0);
		else if(t == 2) add(h, a, b, 1);
		else if(t == 3) add(h, b, a, 0);
		else if(t == 4) add(h, b, a, 1);
		else add(h, a, b, 0);
	}
	
	tarjan(0);  //因为每个点都和超级源点有边,所以直接以超级源点为起点找所有强连通分量即可
	
	bool sucess = true;
	for(int i = 0; i <= n; i ++ ){  //从超级源点开始 
		for(int j = h[i]; ~j; j = ne[j]){
			int k = e[j];
			int a = id[i], b = id[k];
			if(a == b){  //如果存在环 
				if(w[j] > 0){  //存在正环 
					sucess = false;
					break;
				}
			}
			else add(hs, a, b, w[j]);  //用连通块建图 
		}
		if(!sucess) break;
	}
	
	if(!sucess) puts("-1");
	else{  //该遍历求和了 
		for(int i = scc_cnt; i; i -- ){  //按照拓扑序 
			for(int j = hs[i]; ~j; j = ne[j]){  //这里遍历的是按照连通块建立的图 
				int k = e[j];
				dist[k] = max(dist[k], dist[i] + w[j]);
			}
		}
		for(int i = 1; i <= scc_cnt; i ++ ){
			ans += Size[i] * (ll)dist[i];
		}
		cout<<ans<<endl;
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值