【2019 暑假JSOI Day 2 T2】小说(二分+最短路)

博客围绕小S从1号小区到n号小区找小X取小说的问题展开,小S经过主干道耗费体力,小Z可帮k次。题目给出输入输出格式、样例及数据范围。解题思路是利用答案单调性进行二分答案,将问题转化,用dijkstra + 堆优化的check函数求解。

题目

【题目背景】
由于小 X 是一位奆老, 奆老总是忙得一刻也停不下来。 他刚刚准备完食物, 小 X 童年的
挚友小 S 和小 Z 来找他帮忙了……
【题目描述】
小 S 和小 Z 十分喜欢看网络写手“ 2 5 2^5 25” 的小说, 但由于需要付费才能阅读, 而小 S 和小
Z 的零花钱有非常少, 他们只能找小 X 靠黑科技侵入给网站, 把小说给他们。
然而小 X 又非常的爱慕虚荣, 他要小 S 和小 Z 到自己家里来取小说。
小 S、 小 Z 和小 X 都居住在扬中市, 扬中市共有 n n n 个小区, m m m 条主干道(假设每条主干
道都是双行线) 。 小 S 家住在 1 1 1 号小区, 小 X 家住在 n n n 号小区。 小 S 每经过一条主干道需要
耗费 z 点体力, 但由于小 S 的人脉非常广, 每当他到达一个小区, 他都会和好友攀谈直到体
力回满。
由于小 Z 也希望能看到小说, 所以他答应帮助小 S k k k 次, 这 k k k 次小 S 经过主干道不需要
耗费体力。
由于小 S 生性懒惰, 他希望耗费最少的体力到达小 X 家, 请问他最少耗费多少体力?
注意: 如果小 S 到小 X 家可以一路上都由小 Z 背着, 那么体力上限为 0 0 0
如果小 S 到不了小 X 家, 小 S 会很伤心, 体力上限为 − 1 -1 1

【输入格式】
第 1 行三个整数 n , m , k n,m,k n,m,k, 意思如题目描述。
第 2 到第 n+1 行是 x , y , z x,y,z x,y,z 指走连接 x x x 号小区和 y y y 号小区的主干道要耗费 z z z 点体力

【输出格式】
一行一个整数, 表示小 S 最少耗费的体力。

【输入样例】
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6

【输出样例】
4

【样例解释】
小 S 的行走路线: 1 → 3 → 2 → 5 1\rightarrow 3\rightarrow 2\rightarrow 5 1325, 其中 2 → 5 2\rightarrow 5 25 这条主干道由小 Z 帮助小 S 通过。

【数据范围】
对于 30%的数据: n < = 20 ; m < = 100 ; n<=20;m<=100; n<=20;m<=100;
对于 60%的数据: n < = 100 ; m < = 1000 ; n<=100;m<=1000; n<=100;m<=1000;
对于 100%的数据: n < = 1000 ; m < = 10000 ; z < = 1000000 ; n<=1000;m<=10000;z<=1000000; n<=1000;m<=10000;z<=1000000;

【后记】
附上两句 2 5 2^5 25 的语录:
1.扔掉一件东西的时候, 其实扔掉的不止是东西, 记下一件事情的时候, 其实记下的不止是
事情。
2.记在脑海里的东西或许会被忘却, 但记在心里的东西, 是不会消逝的。

思路

这一题我过了(Day 2终于拿了一次第一
答案显然具有单调性,所以可以进行二分答案。
于是,我们可以将问题转化为:有没有一种方法,使体力上限不超过mid。
check函数很好写,只需要把体力 > mid 的路径看做1,把 < mid 的路径看做0,dijkstra + 堆优化 即可(我也不知道要不要加优化,反正我加了)。

代码

#include <bits/stdc++.h>
using namespace std;
const int M = 4010, N = 40100, Z = 1000000;
int n, m, k, dis[M], vis[M], s[M], t[N], e[N], l[N];
struct node {
	int id, dis;
	friend bool operator < (node a, node b) {return a.dis > b.dis;}
}; priority_queue <node> q;

int dijk(int x) {
	memset(vis, 0, sizeof(vis));
	memset(dis, 0x3f3f3f3f, sizeof(dis));
	node f = (node){1, 0};
	dis[1] = 0; q.push(f);
	while (!q.empty()) {
		f = q.top(); q.pop();
		vis[f.id] = 1;
		for (int i = s[f.id]; i != EOF; i = t[i]) {
			int e2 = e[i], k2 = dis[f.id];
			if (vis[e2]) continue;
			if (l[i] >= x) k2++;
			if (dis[e2] > k2) {
				dis[e2] = k2;
				node tmp = (node) {e2, dis[e2]};
				q.push(tmp);
			}
		}
	}
	return dis[n] >= k+1;
}

int main() {
	cin >> n >> m >> k;
	memset(s, -1, sizeof(s));
	int tot = 1, u, v, w;
	for (int i = 1; i <= m; i++) {
		cin >> u >> v >> w;
		t[tot] = s[u], s[u] = tot, e[tot] = v, l[tot] = w, tot++;
		t[tot] = s[v], s[v] = tot, e[tot] = u, l[tot] = w, tot++;
	}
	int l = 0, r = Z+100, ans = -1;
	while (l <= r) {
		int m = (l+r) >> 1;
		if (dijk(m)) l = m+1, ans = m;
		else r = m-1;
	}
	if (ans > Z) cout << -1 << endl;
	else if (r < 0) cout << 0 << endl;
	else cout << ans << endl;
	return 0;
}
考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化与经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本与能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参与调度等方面的有效性,为低碳能源系统的设计与运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模与优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建与求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行二次开发与仿真验证。
<think>我们使用Tarjan算法解决2-SAT问题,并应用于洛谷P4171 [JSOI2010] 满汉全席。 题目描述:有n种食材,每种食材有两种烹饪方式(满式和汉式)。同时有m个评委,每个评委有两个要求,每个要求指定一种食材的烹饪方式(如:食材i必须用满式,或者食材j必须用汉式)。每个评委的要求满足至少一个即可。问是否存在一种烹饪方案满足所有评委的要求。 2-SAT问题:对于每个食材i,定义两个布尔变量: x_i = true 表示第i种食材用满式 x_i = false 表示第i种食材用汉式 但是为了方便,我们通常用两个点表示一个变量的真假(即用两个点表示一个食材的两种选择): 我们用点 i 表示 x_i 为真(即满式) 用点 i+n 表示 x_i 为假(即汉式) 每个评委的两个条件形如:(A 或 B),其中A和B都是关于某个变量的取值要求。 例如:一个评委要求“食材i必须用满式或者食材j必须用汉式”,那么可以表示为:(x_i 为真)(x_j 为假) 这个条件等价于:如果不满足前半部分(即x_i为假),那么必须满足后半部分(即x_j为假);同时,如果不满足后半部分(即x_j为真),那么必须满足前半部分(即x_i为真)。 因此,我们可以将条件转化为两条有向边: ¬A → B 和 ¬B → A 具体地: x_i为假(即点i+n) → x_j为假(点j+n) [因为如果x_i为假,那么必须满足x_j为假] x_j为真(即点j) → x_i为真(点i) [因为如果x_j为真(即不是汉式),那么必须满足x_i为真] 更一般地,对于条件 (A ∨ B),我们可以转化为: ¬A → B 和 ¬B → A 在图中,我们建立对应的有向边。 然后,我们使用Tarjan算法求强连通分量(SCC),并缩点。如果对于某个变量i,其真值点(i)和假值点(i+n)在同一个强连通分量中,则说明无解(因为矛盾:i必须为真又必须为假)。 否则,存在解。 对于输出方案,我们通常利用Tarjan算法得到的SCC编号(即缩点后的编号)来构造。 根据引用[2]和引用[3]:Tarjan算法得到的SCC编号实际上是反拓扑序(即先求出的SCC编号大,后求出的编号小,因为栈底的是先被遍历的,所以编号小)。 而求解2-SAT需要按照逆拓扑序(即从拓扑排序的末尾开始)给变量赋值。 具体规则:对于变量i,我们选择其真值点和假值点中所在SCC编号较小的那个值作为该变量的值(因为较小的编号在拓扑序中更靠后,而按照逆拓扑序求解,我们实际上先处理的是编号大的,然后处理编号小的。但是这里有一个转换:如果变量x的拓扑序在¬x之后,那么取x为真。而Tarjan的SCC编号是反拓扑序,所以x的SCC编号如果小于¬x的编号,那么x的拓扑序实际上在¬x之前(因为反拓扑序中编号小的拓扑序大),那么按照规则应该取x为假?这里需要澄清)。 引用[3]中明确指出:因为Tarjan求得的SCC编号相当于反拓扑序,所以如果x所在SCC编号在¬x之前(即x的编号比¬x的编号小),那么取x为真。为什么呢? 因为逆拓扑序是从拓扑序的末尾开始,而反拓扑序(即Tarjan的SCC编号顺序)中,编号小的节点在拓扑序中实际上是大的(即先出栈的节点在拓扑序中排在后面,后出栈的排在前面?)。实际上,我们通常认为在同一个强连通分量中无所谓,而不同强连通分量之间的边,我们考虑缩点后的图,这个图是有向无环图(DAG)。在缩点后的图中,如果有一条边从SCC1指向SCC2,那么SCC1应该在SCC2之前(拓扑序中SCC1<SCC2)。而Tarjan算法得到的SCC编号是递减的(即先找到的SCC编号大,后找到的SCC编号小),所以SCC编号小的节点在拓扑序中实际上是在后面(即拓扑序是从大到小)。 规则:我们取变量x的值为:如果x的SCC编号小于¬x的SCC编号,则取x为真;否则取假。 为什么?因为这样选择,我们实际上选择了拓扑序较大的那个取值(因为SCC编号小意味着拓扑序大),而按照逆拓扑序(从拓扑序的末尾开始)我们选择的是拓扑序较大的取值,这样不会产生矛盾。 但题目只要求判断是否有解,所以不需要输出方案。 具体步骤: 1. 建图:n个食材,共2*n个点(1到n表示满式,n+1到2*n表示汉式)。 注意:题目输入中,一个评委的两个要求,每个要求是一个字符串,如"m1"表示第1种食材用满式,"h2"表示第2种食材用汉式。 对于每个条件,我们拆分成两个点,并建立两条边。 2. 使用Tarjan算法求所有强连通分量,并记录每个点所在的SCC编号。 3. 检查每个食材i(1<=i<=n): 如果点i和点i+n在同一个强连通分量中,则无解。 4. 如果所有食材都满足点i和点i+n不在同一个强连通分量,则有解。 注意:建图时,需要将输入的字符串转换为对应的点编号。 例如:"m1" -> 点1(表示食材1用满式,即x1为真) "h1" -> 点1+n(表示食材1用汉式,即x1为假) 对于条件:A或B,其中A和B是两个条件(分别对应两个点),那么我们需要建立: ¬A -> B 和 ¬B -> A 如何表示¬A?对于A代表的点p,¬A对应的点就是其对立点(如果p<=n,则对立点是p+n;如果p>n,则对立点是p-n)。 但是,我们也可以这样处理: 设条件A:要么是m要么是h,后面跟一个数字id。 对于条件A,我们定义: 若为m id,则对应的真值点为id,假值点为id+n 若为h id,则对应的真值点为id+n,假值点为id (因为h id表示汉式,即x_id为假,所以用id+n表示这个条件要求假值点?不对,我们要统一变量定义) 重新定义:对于每个食材i,我们定义: 点i:表示采用满式(x_i为真) 点i+n:表示采用汉式(x_i为假) 那么: 条件"m i" 表示:要求点i(即要求x_i为真) 条件"h i" 表示:要求点i+n(即要求x_i为假) 那么,一个评委的两个条件:A 或 B,其中A和B分别是两个条件(分别对应一个点,我们设为a和b)。 那么,这个条件等价于:¬A → B 和 ¬B → A ¬A是什么?如果A是点i(表示要求x_i为真),那么¬A就是x_i为假,即点i+n。 同理,如果B是点j+n(表示要求x_j为假),那么¬B就是x_j为真,即点j。 因此,对于条件A或B,我们添加两条边: 从¬A指向B:即从A的对立点指向B 从¬B指向A:即从B的对立点指向A 具体操作: 设条件A为字符串,如"m1",我们可以得到: 如果第一个字符是'm',则真值点编号为id(整数部分),对立点(假值点)为id+n 如果第一个字符是'h',则真值点编号为id+n,对立点(假值点)为id 注意:这里真值点表示这个条件成立时对应的点,假值点表示这个条件不成立时对应的点。 因此,对于条件A,我们用两个变量表示: 节点a:条件成立时对应的点(即真值点) 节点a的否定:即对立点(即条件不成立时对应的点) 那么,对于条件A或B,我们添加边: 从条件A的否定(即a的对立点)指向条件B的节点(即b) 从条件B的否定(即b的对立点)指向条件A的节点(即a) 建图成后,跑Tarjan,然后检查每个i(1<=i<=n): scc_id[i] 和 scc_id[i+n] 是否相等?如果相等,则无解;否则有解。 代码框架: n, m = 食材种数,评委数 初始化图:共2*n+1个点(点编号从1到2*n) for 每个评委的条件: 读入两个字符串,如s1, s2 将s1转换为节点a(真值点)和它的对立点na 将s2转换为节点b(真值点)和它的对立点nb 添加两条边: na -> b (如果条件1不成立,则条件2必须成立) nb -> a (如果条件2不成立,则条件1必须成立) Tarjan(总点数=2*n) for i in 1..n: if scc_id[i] == scc_id[i+n]: return "BAD" return "GOOD" 注意:Tarjan算法中,我们通常用栈来记录当前搜索的节点,以及记录每个节点的dfn和low。 由于题目只需要判断是否有解,不需要输出方案,因此我们只需要判断每个变量的两个点是否在同一个SCC中。 下面我们根据题目输入输出格式,注意多组数据。 题目输入格式: 第一行:整数T,表示测试数据组数。 每组数据: 第一行:两个整数n, m 接下来m行,每行两个字符串,表示一个评委的两个要求。 输出格式: 每组数据输出一行:如果可行输出"GOOD",否则输出"BAD"。 我们按照上述方法实现即可。 但是,我们也可以考虑优化:建图时,对于每个条件,我们直接得到两个真值点(a和b),以及它们的对立点(na和nb),然后建边na->b和nb->a。 示例:一个评委要求:m1 和 h1 (即食材1用满式或者食材1用汉式) 真值点a:对于"m1" -> 点1 真值点b:对于"h1" -> 点1+n 对立点na:点1的对立点就是1+n 对立点nb:点1+n的对立点就是1 添加边:na->b -> 1+n -> 1+n (边从1+n到1+n?这是自环,可以忽略,但加上也无妨) nb->a -> 1 -> 1 这样建图后,实际上没有添加有效的边?但是注意,这个条件实际上是永真的(因为食材1要么用满式要么用汉式,所以这个条件一定满足)。所以不影响。 因此,我们按照上述方法建图即可。 下面是使用Tarjan算法解决该问题的详细步骤: 1. 初始化:总点数tot=2*n,建图(邻接表),初始化dfn, low, stk, inStack, scc_id等数组,以及时间戳和栈顶指针。 2. 遍历所有点(1到2*n),如果dfn[i]为0,则进行Tarjan(i)。 3. Tarjan算法: dfn[u] = low[u] = ++index; 入栈,标记inStack[u]=true 遍历u的邻接点v: 如果dfn[v]为0,则递归Tarjan(v),然后low[u]=min(low[u], low[v]) 如果dfn[v]不为0且v在栈中,则low[u]=min(low[u], dfn[v]) 如果dfn[u]==low[u]: 将栈中从u到栈顶的所有节点弹出,标记它们属于同一个SCC(scc_count++),并标记这些节点不在栈中。 4. 检查每个i(1<=i<=n): if scc_id[i] == scc_id[i+n] -> 无解。 5. 输出结果。 注意:多组数据,每次要清空图,清空dfn, low, stk, inStack, scc_id等数组,重置时间戳和栈顶指针以及SCC计数。 由于题目数据范围:1<=n<=100, 1<=m<=1000,所以节点200个,边2000条,Tarjan算法全可行。 下面我们用一个例子验证: 例子:输入样例第一组: 2 2 m3 h1 m1 h3 我们建立变量:食材1和食材2(注意题目有2种食材,但条件中出现了3?这里可能是题目描述问题,实际题目中食材编号从1到n,所以样例中应该是3种食材?但题目说2种) 重新检查题目描述:输入格式中,第一个整数n(1<=n<=100)表示食材种数,m(1<=m<=1000)表示评委数。 然后m行,每行两个字符串,每个字符串由一个字符(m或h)和一个数字(1到n)组成。 所以,样例输入中的数字应该不超过n。但是样例输入: 2 2 m3 h1 -> 这里出现了3和1,但n=2,所以食材编号应该是1和2,这里出现了3?可能是题目样例有误?实际上,题目描述中样例输入第一行是1,第二行是2,第三行是3?但题目样例输入如下: 题目样例输入: 2 3 5 m3 h1 m1 m2 m1 h3 h1 m3 h3 m2 2 4 h1 m2 m2 m1 h1 h2 m1 m2 所以,这里第一组数据n=3,m=5,食材编号1,2,3。 因此,我们处理样例第一组: 食材n=3,点编号:1,2,3(满式)和4,5,6(汉式,分别对应1,2,3的汉式) 第一个条件:m3 h1 m3 -> 真值点3,对立点3+3=6 h1 -> 真值点1+3=4,对立点1 添加边:6->4 和 1->3 第二个条件:m1 m2 m1 -> 真值点1,对立点1+3=4 m2 -> 真值点2,对立点2+3=5 添加边:4->2 和 5->1 第三个条件:m1 h3 m1 -> 1,对立点4 h3 -> 3+3=6,对立点3 添加边:4->6 和 3->1 第四个条件:h1 m3 h1 -> 4,对立点1 m3 -> 3,对立点6 添加边:1->3 和 6->4 (注意,这里第一条边1->3已经添加过了,重复添加无影响,但可以避免重复) 第二条边6->4也已经添加过。 第五个条件:h3 m2 h3 -> 6,对立点3 m2 -> 2,对立点5 添加边:3->2 和 5->6 建图成。 然后跑Tarjan,得到SCC,然后检查: 食材1:点1和点4 -> scc_id[1]和scc_id[4]是否相等? 食材2:点2和点5 食材3:点3和点6 如果都不同,则有解,否则无解。 实际题目样例输出第一组:GOOD 第二组:n=2, m=4 条件: h1 m2 -> h1:4(真值点), 对立点1; m2:2,对立点5 -> 边:1->2, 5->4 m2 m1 -> m2:2,对立点5; m1:1,对立点4 -> 边:5->1, 4->2 h1 h2 -> h1:4,对立点1; h2:5,对立点2 -> 边:1->5, 2->4 m1 m2 -> m1:1,对立点4; m2:2,对立点5 -> 边:4->2, 5->1 检查:食材1:点1和点4 -> 它们是否在同一个SCC? 食材2:点2和点5 实际样例输出第二组:BAD 因此,我们按照上述方法实现即可。 由于题目要求,我们只输出是否有解。 现在,我们总结使用Tarjan算法解决P4171的步骤: 1. 读入T(测试数据组数) 2. 对于每组数据: 读入n, m 初始化图:总点数2*n,清空邻接表(大小2*n+1),初始化Tarjan所需数组(dfn, low, inStack, scc_id等),初始化时间戳index=0,栈指针top=0,scc_count=0。 对于m个条件: 读入两个字符串s1, s2 解析s1:第一个字符ch,后面的数字id(字符串转整数) 如果ch=='m',则真值点a = id(注意:id是食材编号,从1开始) 如果ch=='h',则真值点a = id + n 同样解析s2,得到真值点b 得到a的对立点na:如果a<=n,则na=a+n;否则na=a-n 得到b的对立点nb:如果b<=n,则nb=b+n;否则nb=b-n 添加两条有向边:na -> b 和 nb -> a 然后,对每个点(1到2*n)如果dfn[i]为0,则进行Tarjan(i) 检查每个食材i(1<=i<=n): 如果scc_id[i] == scc_id[i+n](注意:i表示满式,i+n表示汉式),则标记无解并跳出循环。 输出结果:如果无解则输出"BAD",否则输出"GOOD" 3. Tarjan算法函数:标准实现。 注意:点编号从1到2*n,所以数组大小要开够。 下面我们写一个伪代码: 由于用户要求使用中文回答,并且需要生成相关问题,我们在回答的后生成相关问题。 但是,用户的问题是:使用tarjan 2-sat算法解决洛谷P4171 [JSOI2010] 满汉全席的方法。 因此,我们回答的重点是方法步骤,而不是代码实现(但可以给出伪代码框架)。 回答结构: 1. 问题转化:将题目条件转化为2-SAT问题。 2. 建图规则:如何根据评委要求建立有向图。 3. 使用Tarjan求强连通分量。 4. 判断是否有解:检查每个变量的两个对立点是否在同一个SCC中。 5. 注意:题目不要求输出方案,所以省略方案输出步骤。 后,生成相关问题。 </think> ### 使用 Tarjan 算法解决 2-SAT 问题(洛谷 P4171) #### 问题转化 1. **变量定义**: 对每种食材 $i$,定义布尔变量: - $x_i = \text{true}$ 表示使用满式烹饪 - $x_i = \text{false}$ 表示使用汉式烹饪 2. **图结构**: - 真值点 $i$ 表示 $x_i$ 为真(满式) - 假值点 $i + n$ 表示 $x_i$ 为假(汉式) 共 $2n$ 个点($1$ 到 $2n$)。 3. **条件转换**: 每个评委的条件如 $(A \lor B)$(例如 `m3 h1`)转换为两条有向边: - $\neg A \to B$(若 $A$ 不成立,则 $B$ 必须成立) - $\neg B \to A$(若 $B$ 不成立,则 $A$ 必须成立) 其中: - `m i` → 真值点 $a = i$,否定点 $\neg a = i + n$ - `h i` → 真值点 $a = i + n$,否定点 $\neg a = i$ #### 算法步骤 1. **建图**: ```python for _ in range(m): s1, s2 = input().split() a = parse(s1) # 解析条件为真值点编号 b = parse(s2) na = a + n if a <= n else a - n # 计算否定点 nb = b + n if b <= n else b - n add_edge(na, b) # 添加 ¬A → B add_edge(nb, a) # 添加 ¬B → A ``` 2. **Tarjan 求强连通分量(SCC)**: - 通过深度优先搜索计算每个点的 `dfn`(访问顺序)和 `low`(回溯值)。 - 将节点压入栈,当 `dfn[u] == low[u]` 时弹出栈中节点形成一个 SCC。 - 为每个 SCC 分配唯一 ID(缩点)。 3. **可行性判断**: 检查每个食材 $i$($1 \leq i \leq n$): $$ \text{scc\_id}[i] == \text{scc\_id}[i + n] \quad \Rightarrow \quad \text{无解} $$ 若任意变量 $x_i$ 的真假值在同一 SCC 中,则矛盾(必须同时为真和假)。 #### 复杂度分析 - **时间复杂度**:$O(n + m)$,Tarjan 算法线性复杂度。 - **空间复杂度**:$O(n + m)$,存储图及 SCC 信息。 #### 参考代码(C++ 核心部分) ```cpp #include <iostream> #include <vector> #include <stack> #include <cstring> using namespace std; const int MAXN = 1000; vector<int> graph[MAXN]; int dfn[MAXN], low[MAXN], scc_id[MAXN]; bool inStack[MAXN]; stack<int> stk; int index = 0, scc_count = 0; void tarjan(int u) { dfn[u] = low[u] = ++index; stk.push(u); inStack[u] = true; for (int v : graph[u]) { if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if (inStack[v]) { low[u] = min(low[u], dfn[v]); } } if (dfn[u] == low[u]) { scc_count++; while (true) { int v = stk.top(); stk.pop(); inStack[v] = false; scc_id[v] = scc_count; if (v == u) break; } } } int main() { int T; cin >> T; while (T--) { int n, m; cin >> n >> m; // 初始化 for (int i = 1; i <= 2 * n; i++) graph[i].clear(); memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(inStack, false, sizeof(inStack)); index = scc_count = 0; // 建图 while (m--) { string s1, s2; cin >> s1 >> s2; int a = stoi(s1.substr(1)) + (s1[0] == 'h' ? n : 0); int b = stoi(s2.substr(1)) + (s2[0] == 'h' ? n : 0); int na = (a <= n) ? a + n : a - n; // 否定点 int nb = (b <= n) ? b + n : b - n; graph[na].push_back(b); graph[nb].push_back(a); } // Tarjan 求 SCC for (int i = 1; i <= 2 * n; i++) if (!dfn[i]) tarjan(i); // 检查可行性 bool valid = true; for (int i = 1; i <= n; i++) { if (scc_id[i] == scc_id[i + n]) { valid = false; break; } } cout << (valid ? "GOOD" : "BAD") << endl; } return 0; } ``` #### 示例分析 - **输入**:`2 4\n h1 m2\n m2 m1\n h1 h2\n m1 m2` - 条件 `h1 m2` → 边 $1 \to 2$ 和 $5 \to 4$ - 终食材 1 的真假点(1 和 4)在同一 SCC → **无解(BAD)**。 --- ### 相关问题 1. **如何证明 2-SAT 问题中同一变量的真假值在同一 SCC 时无解?** [^4] 指出:若 $x_i$ 和 $\neg x_i$ 在同一个 SCC,则存在路径 $x_i \to \neg x_i$ 和 $\neg x_i \to x_i$,导致逻辑矛盾($x_i$ 必须同时为真和假)。 2. **Tarjan 算法求得的 SCC 编号与拓扑序有何关系?** SCC 编号是**反拓扑序**(编号越小,拓扑序越大)。输出解时需选择 SCC 编号较小的值(引用[^2][^3])。 3. **如何处理非布尔逻辑的 2-SAT 扩展问题(如多值变量)?** 可通过增加变量和约束条件转化为标准 2-SAT,但需注意状态空间指数级增长问题。 4. **2-SAT 问题是否有非 Tarjan 的解法?** 是,例如 Kosaraju 算法求 SCC 或随机搜索(如 WalkSAT),但 Tarjan 在效率和确定性上更优。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值