BZOJ 2330 & AcWing 368(判断正环+差分约束)(第三次重新来过)

题目链接 https://www.acwing.com/problem/content/description/370/

这个题,我搞了三天,心态炸裂、、、不得不说,是个好题 

银河中的恒星浩如烟海,但是我们只关注那些最亮的恒星。

我们用一个正整数来表示恒星的亮度,数值越大则恒星就越亮,恒星的亮度最暗是 1。

现在对于 N 颗我们关注的恒星,有 M 对亮度之间的相对关系已经判明。

你的任务就是求出这 N 颗恒星的亮度值总和至少有多大。

输入格式

第一行给出两个整数 N 和 M。

之后 M 行,每行三个整数 T, A, B,表示一对恒星(A, B)之间的亮度关系。恒星的编号从 1 开始。

如果 T = 1,说明 A 和 B 亮度相等。
如果 T = 2,说明 A 的亮度小于 B 的亮度。
如果 T = 3,说明 A 的亮度不小于 B 的亮度。
如果 T = 4,说明 A 的亮度大于 B 的亮度。
如果 T = 5,说明 A 的亮度不大于 B 的亮度。

输出格式

输出一个整数表示结果。

若无解,则输出 -1。

数据范围

N≤100000,M≤100000

首先考虑无解的情况,无解一定发生在环中,而且是一个正环,如果给大于或者小于建立单向边的话,如果对于等于或者大于等于边权为0,大于则边权为1(相减之后大于等于1嘛),如果是一个正环,举例:a>b b = c 同理a > c,但是如果在一个环中说明c和a有关系而且是c大于等于a。因此无解的情况就是判断正环。SPFA跑一下?ohhhh,数据量太大了,SPFA超时,我试了,将队列改为栈依然超时,也就是说这个题故意卡掉了SPFA,只能另辟蹊径。

因为边权非负,也就是说只要环中有一个边的权值为正就是正环,可以联想到强连通分量,线性时间内求解强连通分量,然后缩点建立新图,建立新图的过程中,如果发现两个点属于同一个强连通分量,并且两个点之间有权值为正的边,说明产生了正环直接输出-1结束程序即可。

如果没有正环,那么每一个强连通分量内的边权都是0,也就是说他们的亮度都相同,在缩点后得到的新图中求单源最长路即可,或者按照拓扑序DP也可以。我一开始也尝试了按照拓扑序DFS,爆栈了

注意:需要建立一个超级源点,我觉得莫名其妙,建立超级源点从超级源点开始tarjan就一切正常,而逐个判断dfn[ ]是否为0然后tarjan就爆栈,没道理吧,或者是我太弱了,不懂吧 。还有就是,结果会超出int的范围,因此需要__int64

#include <bits/stdc++.h>
using namespace std;
#define mem(a, b) memset(a, b, sizeof a)
const int N = 100100;
int head[N], nex[N * 4], to[N * 4], ed[N * 4], cnt, head2[N];
int low[N], dfn[N], ind, top, st[N], scc[N], num;
bool vis[N];
vector<int > vec;
int n, m;
bool in[N];
int dist[N];
class Tarjan{
private:
	void add(int* h, int a, int b, int c){
		++cnt;
		to[cnt] = b;
		ed[cnt] = c;
		nex[cnt] = h[a];
		h[a] = cnt;
	}
public:
	Tarjan(){
		mem(head, -1);
		mem(nex, -1);
		mem(head2, -1);
		mem(low, 0);
		mem(dfn, 0);
		mem(scc, -1);
		ind = 0;
		top = 0;
		num = 0;
		vec.clear();
		mem(in, 0);
		mem(dist, 0);
	}
	void init(){
		Tarjan();
	}
	void _add(int* h, int t, int a, int 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);
	}
	void tarjan(int x){
		low[x] = dfn[x] = ++num;
		st[++top] = x;
		vis[x] = 1;
		for (int i = head[x]; i != -1; i = nex[i]){
			int y = to[i];
			if (dfn[y] == 0){
				tarjan(y);
				low[x] = min(low[x], low[y]);
			}
			else if (vis[y])low[x] = min(low[x], dfn[y]);
		}
		if (low[x] == dfn[x]){
			++ind;
			int number = 0;
			for (;;){
				int t = st[top--];
				vis[t] = 0;
				scc[t] = ind;
				number++;
				if (t == x)break;
			}
			vec.push_back(number);
		}
	}
	bool rebuild(){
		for (int i = 0; i <= n; i++){
			for (int j = head[i]; j != -1; j = nex[j]){
				int y = to[j];
				int c = ed[j];
				if (scc[y] == scc[i]){
					if (c > 0)return false;
				}
				else {
					add(head2, scc[i], scc[y], c);
					in[scc[y]] = 1;
				}
			}
		}
		return true;
	}
};
Tarjan tar;
int main()
{
	tar.init();
	scanf("%d %d", &n, &m);
	int t, a, b;
	for (int i = 1; i <= m; i++){
		scanf("%d %d %d", &t, &a, &b);
		tar._add(head, t, a, b);
	}
	for (int i = 1; i <= n; i++)tar._add(head, 2, 0, i);
	tar.tarjan(0);
	bool f = tar.rebuild();
	if (!f){
		printf("-1\n");
	}
	else{
		for (int i = ind; i >= 1; i--){// 拓扑序DP
			for (int j = head2[i]; j != -1; j = nex[j]){
				int y = to[j];
				int c = ed[j];
				if (dist[y] < dist[i] + c){// 求最长路
					dist[y] = dist[i] + c;
				}
			}
		}
		long long ans = 0;
		for (int i = 1; i <= ind; i++){
			ans += dist[i] * vec[i - 1];
		}
		printf("%lld\n", ans);
	}
	return 0;
}

上面的每一个错误我都犯了不止一遍,QAQ 

发布了204 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43701790/article/details/104962694