前言

由于蒟蒻才刚开始学 \(\text{2-SAT}\),所以题解中有的地方可能不够精炼,望多包涵!

题目描述

题目意思很简单,标准的\(\text{2-SAT}\)问题模型。那么我们就先来介绍一下 \(\text{2-SAT}\) (以下是个人的小小概括)

\(\text{2-SAT}\) 问题,抽象化一下,是这样的:

给出 \(n\) 个布尔变量 \(\{x_n\}\),以及 \(m\) 个命题 \((a,aa,b,bb)\),一个命题成立的条件是\([x_a=aa]\lor[x_b=bb]\)

现在就是要判断是否一种方案,将\(\{x_n\}\)中的每个元素赋一个值,使所有的\(m\)个命题成立

对于这种问题,我们用一下方法来建立图的模型:

对于 \(n\) 个不同变量 \(x\),我们将其拆成两个点,分别表示 \(x\) 为真和 \(x\) 为假(可以用 \(i\) 表示 \(x_i\) 为真,\(i+n\) 表示 \(x_i\) 为假)

接下来对于每一条有向边 \((a,b)\),我们赋予它这样的意义:若 \(a\) 应该被满足,则 \(b\) 也必须被满足

这样一来,我们就可以用如下的方法判定有无解:

  • 有解的情况:\(\forall\ i\in n\), \(i\) 和 \(i+n\) 不属于同一个强连通分量。
  • 无解的情况:\(\exists\ i\in n\), \(i\) 和 \(i+n\) 属于同一个强连通分量。

因为按照我们上面的建边方法,属于同一个强连通分量的两个点他们所代表的命题是要同时为真的。

而因为同一个布尔变量 \(x\) 不会同时有两种值,所以以上判断方法的正确性是显然的。

那么我们就只需建好图,跑一遍 \(\text{Tarjan}\) 再按照上述方法判断即可。

那么,接下来就是最重要的一步:如何建图?

其实这并不难,只要能抽象出 \(\{x_n\}\) 和 \(m\) 个命题就好,以这道题为例,加深一下理解。


基本思路

我们把\(n\)样食材抽象成\(\{x_n\}\),第 \(i\) 样食材做成汉式表示 \(x_i\) 为真(点 \(i\)),反之表示 \(x_i\) 为假(点 \(i+n\))

然后对于每一位评审的需求,也类似地按照上面的方法抽象一下

然后对于每一项需求给出的两个命题 \(p,q\),我们连两条边 \((\lnot p,q)\) 和 \((\lnot q, p)\)

(至于这里的 \(p\) 和 \(q\)是什么,可以自己思考一下)


细节注意事项

  • 由于我们的 \(n\) 样食材会被拆成两个点,所以点的空间要开两倍。
  • 由于我们的 \(m\) 项需求会产生两条边,所以边的空间也要开两倍。
  • 每一次初始化时,如果用\(\text{for}\)循环清空数组,千万要注意枚举的上界(见上两条)。

参考代码

可能我写的不是很好,没有看明白的话可以结合我的代码理解给个好评吧啊啊啊

/*--------------------------------
Code name: meal.cpp
Author: The Ace Bee
This code is made by The Ace Bee
--------------------------------*/
#include <cstdio>
#include <cstring>
#define rg register
#define fileopen(x) \
freopen(x".in", "r", stdin); \
freopen(x".out", "w", stdout);
#define fileclose \
fclose(stdin); \
fclose(stdout);
const int MAXN = 233;
const int MAXM = 2333;
inline int min(int a, int b) { return a < b ? a : b; }
inline int read() {
int s = 0; bool f = false; char c = getchar();
while (c < '0' || c > '9') f |= (c == '-'), c = getchar();
while (c >= '0' && c <= '9') s = (s << 3) + (s << 1) + (c ^ 48), c = getchar();
return f ? -s : s;
}
int tot, head[MAXN], nxt[MAXM], ver[MAXM];
inline void Add_edge(int u, int v)
{ nxt[++tot] = head[u], head[u] = tot, ver[tot] = v; }
int n, num, dfn[MAXN], low[MAXN];
int st[MAXN], top, co[MAXN], col;
inline void tarjan(int u) {
dfn[u] = low[u] = ++num, st[++top] = u;
for (rg int v, i = head[u]; i; i = nxt[i]) {
if (!dfn[v = ver[i]])
tarjan(v), low[u] = min(low[u], low[v]);
else
if (!co[v]) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
++col;
do co[st[top]] = col, --top;
while (st[top + 1] != u);
}
}
inline void init() {
tot = col = num = top = 0;
memset(co, 0, sizeof co);
memset(dfn, 0, sizeof dfn);
memset(low, 0, sizeof low);
memset(head, 0, sizeof head);
}
int main() {
// fileopen("meal");
char sa[10], sb[10];
for (rg int T = read(); T; --T) {
init();
int n = read();
for (rg int m = read(); m; --m) {
scanf("%s%s", sa, sb);
int a = 0, lena = strlen(sa);
for (rg int i = 1; i < lena; ++i)
a = (a << 3) + (a << 1) + (sa[i] ^ 48);
int b = 0, lenb = strlen(sb);
for (rg int i = 1; i < lenb; ++i)
b = (b << 3) + (b << 1) + (sb[i] ^ 48);
if (sa[0] == 'h' && sb[0] == 'h')
Add_edge(a + n, b), Add_edge(b + n, a);
else if (sa[0] == 'h' && sb[0] == 'm')
Add_edge(a + n, b + n), Add_edge(b, a);
else if (sa[0] == 'm' && sb[0] == 'h')
Add_edge(a, b), Add_edge(b + n, a + n);
else if (sa[0] == 'm' && sb[0] == 'm')
Add_edge(a, b + n), Add_edge(b, a + n);
}
for (rg int i = 1; i <= n << 1; ++i)
if (!dfn[i]) tarjan(i);
int flag = 1;
for (rg int i = 1; i <= n; ++i)
if (co[i] == co[i + n]) { flag = 0; break; }
puts(flag ? "GOOD" : "BAD");
}
// fileclose;
return 0;
}

完结撒花 \(qwq\)

「JSOI2010」满汉全席的更多相关文章

  1. LG4171/BZOJ1823 「JSOI2010」满汉全席 2-SAT

    问题描述 LG4171 BZOJ1823 题解 显然,每个评委对每个材料的满式/汉式要求是对\(n\)个元素的\(0,1\)取值限制. 显然想到\(\mathrm{2-SAT}\) 于是就可以切掉了. ...

  2. 「JSOI2010」排名

    「JSOI2010」排名 传送门 看到先后顺序限制和字典序,很容易想到拓扑排序 + 贪心. 考虑具体做法: 对于第一问: 我们开一个大根堆来代替队列,然后从大到小构造出各个元素的排名. 我们连边 \( ...

  3. 「JSOI2010」挖宝藏

    「JSOI2010」挖宝藏 传送门 由于题目中说道挖一个位置的前提是挖掉它上面的三个,以此类推可以发现,挖掉一个点就需要挖掉这个点往上的整个倒三角,那么也就会映射到 \(x\) 轴上的一段区间(可以发 ...

  4. 「JSOI2010」找零钱的洁癖

    「JSOI2010」找零钱的洁癖 传送门 个人感觉很鬼的一道题... 首先我们观察到不同的数最多 \(50\) 个,于是考虑爆搜. 但是这样显然不太对啊,状态数太多了. 然后便出现了玄学操作: \(\ ...

  5. 「JSOI2010」旅行

    「JSOI2010」旅行 传送门 比较妙的一道 \(\text{DP}\) 题,思维瓶颈应该就是如何确定状态. 首先将边按边权排序. 如果我们用 \(01\) 串来表示 \(m\) 条边是否在路径上, ...

  6. 「译」JUnit 5 系列:条件测试

    原文地址:http://blog.codefx.org/libraries/junit-5-conditions/ 原文日期:08, May, 2016 译文首发:Linesh 的博客:「译」JUni ...

  7. 「译」JUnit 5 系列:扩展模型(Extension Model)

    原文地址:http://blog.codefx.org/design/architecture/junit-5-extension-model/ 原文日期:11, Apr, 2016 译文首发:Lin ...

  8. JavaScript OOP 之「创建对象」

    工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程.工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题. function createPers ...

  9. 「C++」理解智能指针

    维基百科上面对于「智能指针」是这样描述的: 智能指针(英语:Smart pointer)是一种抽象的数据类型.在程序设计中,它通常是经由类型模板(class template)来实做,借由模板(tem ...

随机推荐

  1. socket模块(套接字模块)

    socket模块(套接字模块) 一.最简单版本(互传一次就结束) # 客户端 import socket client = socket.socket() client.connect(('127.0 ...

  2. [C/C++] _tprintf() 输出不了汉字

    在前面加一个 setlocale(LC_ALL, ""); //必须得有这行 否则不能输出中文 注意得加locale头文件 #include<stdio.h> #inc ...

  3. NOIP--模拟—————神奇的幻方

    神奇的幻方 题目描述 幻方是一种很神奇的 N*N 矩阵:它由数字 1,2,3,-N x N 构成,且每行.每列及两条对角线上的数字之和都相同. 当 N 为奇数时,我们可以通过下方法构建一个幻方: 首先 ...

  4. PS——牛奶字

    一.新建800*600像素的背景,设置前景色到透明渐变(黑到白),线性渐变,从上到下画一条直线 二.用矩形选框工具在背景上方1/2位置画一个矩形,Ctrl+Delete填充颜色 三.输入文字,设置图层 ...

  5. Tiny-shell

    Tiny-shell:一个模仿bash的极简shell (一) 概述 通过构建一个简单的shell,能够对shell的工作原理进行一些了解.主要有: 重定向 流水线 前台信号处理 进程组 后台进程 作 ...

  6. MySQL双机热备环境搭建

    一.    前期准备 准备两台服务器(电脑),接入到同一局域网中,能够使双方可以ping通: 安装MySQL数据库,具体安装方法网上很全面,但是安装的版本需保持一致: 服务器IP地址设置. l  A服 ...

  7. 【嵌入式】ARM9复习

    一.嵌入式系统基础 二.ARM处理器 1. 在每条指令后,用;//注释这条指令的寻址方式,以及实现的功能(25分) 注:变址寻址需要标注是基址加偏移.还是基址加索引,是前变址还是后变址.SUB SP, ...

  8. Codeforces Round #576 (Div. 2) 题解

    比赛链接:https://codeforc.es/contest/1199 A. City Day 题意:给出一个数列,和俩个整数\(x,y\),要求找到序号最靠前的数字\(d\),使得\(d\)满足 ...

  9. 5种JVM调优配置方法概览!!!

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  10. Java学习资源 - J2EE

    java Web开发基础(一)工程项目文档结构 ========rmi=========== Java RMI 框架(远程方法调用) java RMI原理详解 深究Java中的RMI底层原理 ==== ...