2-SAT很多时候的应用场景是,给定一个事物,这个事物有两种状态选择。基本就是这个情况,详细的话,通过下面的样例与题集来进行更深入的理解。
例题:
Party-HDU 3062
Katu Puzzle-POJ 3678
实战:
Get Luffy Out *-HDU1816
Go Deeper-HDU 3715
中等题:
Eliminate the Conflict-HDU 4115
Bit Magic-HDU 4421
Party-HDU 3062
传送门
这道题有两种矛盾关系:
- 一对夫妻中只能一人出席
- 有矛盾的两人不能同时出席
首先根据第一个矛盾关系,可以确定是2-SAT的题目,假设一对夫妻就是一种事物,只能丈夫去或者妻子去,便是两种选择。其次再看第二个关系,有矛盾的不能同时出席, i . e . i.e. i.e. 1号丈夫和2号妻子不能同时出席,那么如果1号丈夫出席了,那另一对中只能够2号丈夫去;如果2号妻子去了,那另一对中只能1号妻子去。但反过来看,如果1号妻子去了,2号夫妇谁去都无所谓;同样如果2号丈夫去了,1号夫妇谁去也无所谓。
2-SAT就是对矛盾关系中进行建边,当有矛盾关系的两个事物中,第一个事物的一项选择选上的话,另一个事物只能被迫选其中一项才能成立的时候,这时就需要进行建边引导。像上面如果1号妻子去了,不需要进行建边,2号选谁都没问题,这个时候1号妻子去了必定有解。
根据夫妻编号是从0~n-1,现假设建边的时候夫妻编号是 x x x,妻子是 ( x < < 1 ) (x<<1) (x<<1),丈夫是 ( x < < 1 ∣ 1 x<<1|1 x<<1∣1) 。
- 题目的建边便是 a d d ( A 1 < < 1 ∣ C 1 , A 2 < < 1 ∣ C 2 ⊕ 1 ) add(A1<<1|C1,A2<<1|C2\oplus1) add(A1<<1∣C1,A2<<1∣C2⊕1), a d d ( A 2 < < 1 ∣ C 2 , A 1 < < 1 ∣ C 1 ⊕ 1 ) add(A2<<1|C2,A1<<1|C1\oplus1) add(A2<<1∣C2,A1<<1∣C1⊕1)
- 其中 A 1 < < 1 ∣ C 1 A1<<1|C1 A1<<1∣C1 就是表示夫妻编号为 A 1 A1 A1当中的妻子或者丈夫,异或就是取对立面,比如 A 2 A2 A2中的丈夫的话,异或后便是 A 2 A2 A2中的妻子。
- 建边完成后跑一遍 2 − S A T 2-SAT 2−SAT 的板子即可。
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <set>
#include <cstdlib>
#include <iostream>
#include <map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uii;
typedef pair<int, ll> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps = 1e-5;
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x > 0) return 1;
return -1;
}
int n, m, st, ed;
int head[N], cntE;
struct edge {
int to, next;
int w;
}e[M];
void add(int u, int v, int w = 0) {
e[cntE].to = v;
e[cntE].next = head[u];
e[cntE].w = w;
head[u] = cntE++;
}
bool vis[N];
int stk[N], top;
bool dfs(int x) {
if (vis[x ^ 1]) return false;
if (vis[x]) return true;
vis[x] = true;
stk[++top] = x;
for (int i = head[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (!dfs(v)) return false;
}
return true;
}
bool two_sat() {
memset(vis, false, sizeof(bool) * (n * 2 + 10));
for (int i = 0; i < n; ++i) {
if (vis[i << 1] || vis[i << 1 | 1]) continue;
top = 0;
if (!dfs(i << 1)) {
while (top) vis[stk[top--]] = false;
if (!dfs(i << 1 | 1)) return false;
}
}
return true;
}
int main() {
#ifdef _DEBUG
FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif // !_DEBUG
int cas = 0;
while (~scanf("%d", &n)) {
rd(m);
memset(head, -1, sizeof(int) * (n * 2 + 10)); cntE = 0;
while (m--) {
int x, y, i, j; rd(x), rd(y), rd(i), rd(j);
add(x << 1 | i, y << 1 | j ^ 1);
add(y << 1 | j, x << 1 | i ^ 1);
}
if (two_sat()) puts("YES");
else puts("NO");
}
return 0;
}
Katu Puzzle
- 题意是给定一个数组 x x x 的长度为 n n n 。然后给出 x i x_i xi o p op op x j = c x_j=c xj=c,其中 c = 0 或 者 1 c=0或者1 c=0或者1,然后 o p op op: A N D AND AND O R OR OR X O R XOR XOR
- 假设数组下标为 i i i ,则 ( i < < 1 ) (i<<1) (i<<1) 表示 x i = 0 x_i=0 xi=0; ( i < < 1 ∣ 1 ) (i<<1|1) (i<<1∣1) 表示 x i = 1 x_i=1 xi=1
- 若 x i & x j = 1 x_i\&x_j=1 xi&xj=1,显然只能两者同时为1才有解: a d d ( i < < 1 ∣ 1 , j < < 1 ∣ 1 ) , a d d ( j < < 1 ∣ 1 , i < < 1 ∣ 1 ) add(i<<1|1,j<<1|1),add(j<<1|1,i<<1|1) add(i<<1∣1,j<<1∣1),add(j<<1∣1,i<<1∣1);若其中一个为0,则无解,无解让他自相矛盾即可: a d d ( i < < 1 , i < < 1 ∣ 1 ) , a d d ( j < < 1 , j < < 1 ∣ 1 ) add(i<<1,i<<1|1),add(j<<1,j<<1|1) add(i<<1,i<<1∣1),add(j<<1,j<<1∣1);
- 若 x i & x j = 0 x_i\&x_j=0 xi&xj=0,若 x i = 1 x_i=1 xi=1,只能令 x j = 0 x_j=0 xj=0 才有解: a d d ( i < < 1 ∣ 1 , j < < 1 ) , a d d ( j < < 1 ∣ 1 , i < < 1 ) add(i<<1|1,j<<1),add(j<<1|1,i<<1) add(i<<1∣1,j<<1),add(j<<1∣1,i<<1);若 x i = 0 x_i=0 xi=0,则不管 x j x_j xj 为0或者为1,答案都可行,所以无需建边。 2 − S A T 2-SAT 2−SAT 只对唯一情况建边。
- 同样或运算和异或运算建边一个道理。
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <set>
#include <cstdlib>
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uii;
typedef pair<int, ll> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps = 1e-5;
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x > 0) return 1;
return -1;
}
int n, m, st, ed, k;
int head[N], cntE;
struct edge {
int to, next;
int w;
}e[M];
void add(int u, int v, int w = 0) {
e[cntE].to = v;
e[cntE].next = head[u];
e[cntE].w = w;
head[u] = cntE++;
}
bool vis[N];
int stk[N], top;
bool dfs(int x) {
if (vis[x ^ 1]) return false;
if (vis[x]) return true;
vis[x] = true;
stk[++top] = x;
for (int i = head[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (!dfs(v)) return false;
}
return true;
}
bool two_sat() {
memset(vis, false, sizeof(bool) * (2 * n + 10));
for (int i = 0; i < n; ++i) {
if (vis[i << 1] || vis[i << 1 | 1]) continue;
top = 0;
if (!dfs(i << 1)) {
while (top) vis[stk[top--]] = false;
if (!dfs(i << 1 | 1)) return false;
}
}
return true;
}
int main() {
#ifdef _DEBUG
FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif // !_DEBUG
int cas = 0, T;
// rd(T);
// while (T--) {
while (~scanf("%d %d", &n, &m)) {
memset(head, -1, sizeof(int) * (2 * n + 10)); cntE = 0;
while (m--) {
int x, y, c; char op[5];
scanf("%d %d %d %s", &x, &y, &c, op);
if (op[0] == 'A') {
if (c) {
add(x << 1 | 1, y << 1 | 1); add(y << 1 | 1, x << 1 | 1);
add(x << 1, x << 1 | 1); add(y << 1, y << 1 | 1);
}
else {
add(x << 1 | 1, y << 1);
add(y << 1 | 1, x << 1);
}
}
else if (op[0] == 'O') {
if (c) {
add(x << 1, y << 1 | 1);
add(y << 1, x << 1 | 1);
}
else {
add(x << 1, y << 1); add(y << 1, x << 1);
add(x << 1 | 1, x << 1);
add(y << 1 | 1, y << 1);
}
}
else {
if (c) {
add(x << 1, y << 1 | 1); add(y << 1 | 1, x << 1);
add(x << 1 | 1, y << 1); add(y << 1, x << 1 | 1);
}
else {
add(x << 1, y << 1); add(y << 1, x << 1);
add(x << 1 | 1, y << 1 | 1); add(y << 1 | 1, x << 1 | 1);
}
}
}
if (two_sat()) puts("YES");
else puts("NO");
}
return 0;
}
Get Luffy Out *
- 这道题的做法,显然是二分答案+ 2 − S A T 2-SAT 2−SAT
- 建边关系有:假设一对钥匙下标为 x 与 y x与y x与y, ( x < < 1 ) 表 示 x 未 使 用 , ( x < < 1 ∣ 1 ) 表 示 钥 匙 使 用 了 (x<<1)表示x未使用,(x<<1|1)表示钥匙使用了 (x<<1)表示x未使用,(x<<1∣1)表示钥匙使用了,使用其中一把,另一把就会消失,所以建边 a d d ( x < < 1 ∣ 1 , y < < 1 ) , a d d ( y < < 1 ∣ 1 , x < < 1 ) add(x<<1|1,y<<1),add(y<<1|1,x<<1) add(x<<1∣1,y<<1),add(y<<1∣1,x<<1)
- 下面开门中,选择其中一个开门即可,所以要把门打开,必须有钥匙匹配,若其中一把钥匙不匹配,另一把必定要匹配到才有解: a d d ( x < < 1 , y < < 1 ∣ 1 ) , a d d ( y < < 1 , x < < 1 ∣ 1 ) add(x<<1,y<<1|1),add(y<<1,x<<1|1) add(x<<1,y<<1∣1),add(y<<1,x<<1∣1) ;若有钥匙可以使用,则另一把钥匙用不用都无所谓,所以无需建边,保证有解即可。
- 接下来就是二分能够到达的层数即可
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <set>
#include <cstdlib>
#include <iostream>
#include <map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uii;
typedef pair<int, ll> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps = 1e-5;
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x > 0) return 1;
return -1;
}
int n, m, st, ed, k;
int head[N], cntE;
struct edge {
int to, next;
int w;
}e[M];
void add(int u, int v, int w = 0) {
e[cntE].to = v;
e[cntE].next = head[u];
e[cntE].w = w;
head[u] = cntE++;
}
bool vis[N];
int stk[N], top;
bool dfs(int x) {
if (vis[x ^ 1]) return false;
if (vis[x]) return true;
vis[x] = true;
stk[++top] = x;
for (int i = head[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (!dfs(v)) return false;
}
return true;
}
bool two_sat() {
memset(vis, false, sizeof(bool) * (n * 4 + 10));
for (int i = 0; i < n * 2; ++i) {
if (vis[i << 1] || vis[i << 1 | 1]) continue;
top = 0;
if (!dfs(i << 1)) {
while (top) vis[stk[top--]] = false;
if (!dfs(i << 1 | 1)) return false;
}
}
return true;
}
int a[N], b[N], c[N], d[N];
bool check(int x) {
memset(head, -1, sizeof(int) * (n * 4 + 10)); cntE = 0;
for (int i = 1; i <= n; ++i) {
add(a[i] << 1 | 1, b[i] << 1);
add(b[i] << 1 | 1, a[i] << 1);
}
for (int i = 1; i <= x; ++i) {
add(c[i] << 1, d[i] << 1 | 1);
add(d[i] << 1, c[i] << 1 | 1);
}
if (two_sat()) return true;
else return false;
}
int main() {
#ifdef _DEBUG
FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif // !_DEBUG
int cas = 0, T;
// rd(T);
// while (T--) {
while (~scanf("%d %d", &n,&m),n+m) {
for (int i = 1; i <= n; ++i) {
rd(a[i]); rd(b[i]);
}
for (int i = 1; i <= m; ++i) {
rd(c[i]); rd(d[i]);
}
int l = 1, r = m;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) l = mid + 1;
else r = mid - 1;
}
printf("%d\n", l - 1);
}
return 0;
}
Go Deeper
- 同样是二分答案+ 2 − S A T 2-SAT 2−SAT ,设 ( x i < < 1 ) 为 x i = 0 , ( x i < < 1 ∣ 1 ) 为 x i = 1 ; (x_i<<1)为x_i=0,(x_i<<1|1)为x_i=1; (xi<<1)为xi=0,(xi<<1∣1)为xi=1;
- 当 x i + x j = 0 x_i+x_j=0 xi+xj=0 ,建边 a d d ( i < < 1 , j < < 1 ∣ 1 ) , a d d ( j < < 1 , i < < 1 ∣ 1 ) add(i<<1,j<<1|1),add(j<<1,i<<1|1) add(i<<1,j<<1∣1),add(j<<1,i<<1∣1) ;若其中一个状态为1,另一个无论如何都不可能相加得0,必定有解无须建边。
- 同样的套路用在其余两个上, 详情见代码
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <set>
#include <cstdlib>
#include <iostream>
#include <map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uii;
typedef pair<int, ll> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps = 1e-5;
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x > 0) return 1;
return -1;
}
int n, m, st, ed;
int head[N], cntE;
struct edge {
int to, next;
int w;
}e[M];
void add(int u, int v, int w = 0) {
e[cntE].to = v;
e[cntE].next = head[u];
e[cntE].w = w;
head[u] = cntE++;
}
bool vis[N];
int stk[N], top;
bool dfs(int x) {
if (vis[x ^ 1]) return false;
if (vis[x]) return true;
vis[x] = true;
stk[++top] = x;
for (int i = head[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (!dfs(v)) return false;
}
return true;
}
bool two_sat() {
memset(vis, false, sizeof(bool) * (n * 2 + 10));
for (int i = 0; i < n; ++i) {
if (vis[i << 1] || vis[i << 1 | 1]) continue;
top = 0;
if (!dfs(i << 1)) {
while (top) vis[stk[top--]] = false;
if (!dfs(i << 1 | 1)) return false;
}
}
return true;
}
int x[N], y[N], w[N];
bool check(int mid) {
memset(head, -1, sizeof(int) * (n * 2 + 10)); cntE = 0;
for (int i = 1; i <= mid; ++i) {
if (w[i] == 0) {
add(x[i] << 1, y[i] << 1 | 1);
add(y[i] << 1, x[i] << 1 | 1);
}
else if (w[i] == 1) {
add(x[i] << 1, y[i] << 1);
add(y[i] << 1, x[i] << 1);
add(x[i] << 1 | 1, y[i] << 1 | 1);
add(y[i] << 1 | 1, x[i] << 1 | 1);
}
else {
add(x[i] << 1 | 1, y[i] << 1);
add(y[i] << 1 | 1, x[i] << 1);
}
}
if (two_sat()) return true;
else return false;
}
int main() {
#ifdef _DEBUG
FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif // !_DEBUG
int cas = 0, T;
rd(T);
while (T--) {
// while (~scanf("%d", &n)) {
rd(n), rd(m);
for (int i = 1; i <= m; ++i) {
rd(x[i]), rd(y[i]), rd(w[i]);
}
int l = 1, r = m;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) l = mid + 1;
else r = mid - 1;
}
printf("%d\n", l - 1);
}
return 0;
}
Eliminate the Conflict
- 这题很难想到 2 − S A T 2-SAT 2−SAT , 2 − S A T 2-SAT 2−SAT 需要用到一个事物的两种状态,而这道题,若想要赢,在满足所有条件的情况下,在每一轮的游戏中:要么赢,要么平局。而这个便是两种不同的状态,赢或者平局。
- 对应下先处理出每一局的胜利和平局的表示。
- 对于有约束关系的 i 和 j i和j i和j,若其中一个状态不满足,则必须与对面另一个状态建边,把握好这个思想就可以了,详情看代码吧。
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <set>
#include <cstdlib>
#include <iostream>
#include <map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uii;
typedef pair<int, ll> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps = 1e-5;
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x > 0) return 1;
return -1;
}
int n, m, st, ed, k;
int head[N], cntE;
struct edge {
int to, next;
int w;
}e[M];
void add(int u, int v, int w = 0) {
e[cntE].to = v;
e[cntE].next = head[u];
e[cntE].w = w;
head[u] = cntE++;
}
bool vis[N];
int stk[N], top;
bool dfs(int x) {
if (vis[x ^ 1]) return false;
if (vis[x]) return true;
vis[x] = true;
stk[++top] = x;
for (int i = head[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (!dfs(v)) return false;
}
return true;
}
bool two_sat() {
memset(vis, false, sizeof(bool) * (n * 2 + 10));
for (int i = 0; i < n; ++i) {
if (vis[i << 1] || vis[i << 1 | 1]) continue;
top = 0;
if (!dfs(i << 1)) {
while (top) vis[stk[top--]] = false;
if (!dfs(i << 1 | 1)) return false;
}
}
return true;
}
int a[N][2];
int main() {
#ifdef _DEBUG
FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif // !_DEBUG
int cas = 0, T;
rd(T);
while (T--) {
// while (~scanf("%d %d", &n,&m)) {
rd(n), rd(m);
for (int i = 1; i <= n; ++i) {
rd(a[i][0]); --a[i][0];
a[i][1] = (a[i][0] + 1) % 3;
}
memset(head, -1, sizeof(int) * (n * 2 + 10)); cntE = 0;
while (m--) {
int x, y, op; rd(x), rd(y), rd(op);
if (!op) {
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
if (a[x][i] != a[y][j]) {
add(x << 1 | i, y << 1 | j ^ 1);
add(y << 1 | j, x << 1 | i ^ 1);
}
}
}
}
else {
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
if (a[x][i] == a[y][j]) {
add(x << 1 | i, y << 1 | j ^ 1);
add(y << 1 | j, x << 1 | i ^ 1);
}
}
}
}
}
printf("Case #%d: ", ++cas);
if (two_sat()) puts("yes");
else puts("no");
}
return 0;
}
Bit Magic
- 这题是可以用到 2 − S A T 2-SAT 2−SAT 的,需要用到两种状态的话,很容易想到,对数组b的所有数进行二进制转换。转换后对应1-31位只有0或者1两种状态,应用这个状态进行判定即可。
- 在建边的详情中与上面第二题原理一样,就不多解释
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <set>
#include <cstdlib>
#include <iostream>
#include <map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uii;
typedef pair<int, ll> pii;
template<typename T>
inline void rd(T& x)
{
int tmp = 1; char c = getchar(); x = 0;
while (c > '9' || c < '0') {
if (c == '-')tmp = -1; c = getchar(); }
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar(); }
x *= tmp;
}
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double eps = 1e-5;
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x > 0) return 1;
return -1;
}
int n, m, st, ed, k;
int head[N], cntE;
struct edge {
int to, next;
int w;
}e[M];
void add(int u, int v, int w = 0) {
e[cntE].to = v;
e[cntE].next = head[u];
e[cntE].w = w;
head[u] = cntE++;
}
bool vis[N];
int stk[N], top;
bool dfs(int x) {
if (vis[x ^ 1]) return false;
if (vis[x]) return true;
vis[x] = true;
stk[++top] = x;
for (int i = head[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (!dfs(v)) return false;
}
return true;
}
bool two_sat() {
memset(vis, false, sizeof(bool) * (n * 2 + 10));
for (int i = 0; i < n; ++i) {
if (vis[i << 1] || vis[i << 1 | 1]) continue;
top = 0;
if (!dfs(i << 1)) {
while (top) vis[stk[top--]] = false;
if (!dfs(i << 1 | 1)) return false;
}
}
return true;
}
int b[505][505];
bool check() {
for (int x = 0; x < 31; ++x) {
memset(head, -1, sizeof(int) * (n * 2 + 10)); cntE = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if ((i & 1) && (j & 1)) {
if (b[i][j] & (1 << x)) {
add(i << 1, j << 1 | 1);
add(j << 1, i << 1 | 1);
}
else {
add(i << 1, j << 1);add(j << 1, i << 1);
add(i << 1 | 1, i << 1);
add(j << 1 | 1, j << 1);
}
}
else if (!(i & 1) && !(j & 1)) {
if (b[i][j] & (1 << x)) {
add(i << 1 | 1, j << 1 | 1); add(j << 1 | 1, i << 1 | 1);
add(i << 1, i << 1 | 1);
add(j << 1, j << 1 | 1);
}
else {
add(i << 1 | 1, j << 1);
add(j << 1 | 1, i << 1);
}
}
else {
if (b[i][j] & (1 << x)) {
add(i << 1 | 1, j << 1);add(j << 1 | 1, i << 1);
add(i << 1, j << 1 | 1);add(j << 1, i << 1 | 1);
}
else {
add(i << 1, j << 1); add(j << 1, i << 1);
add(i << 1 | 1, j << 1 | 1); add(j << 1 | 1, i << 1 | 1);
}
}
}
}
if (!two_sat()) return false;
}
return true;
}
int main() {
#ifdef _DEBUG
FILE* _INPUT = freopen("input.txt", "r", stdin);
// FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif // !_DEBUG
int cas = 0, T;
// rd(T);
// while (T--) {
while (~scanf("%d", &n)) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
rd(b[i][j]);
}
}
if (check()) puts("YES");
else puts("NO");
}
return 0;
}