← Home
#include <bits/stdc++.h>
using namespace std;
#define __Yang_Yang__() int main()
int test = 1;
using ll = long long;
using ull = unsigned long long;
using ldb = long double;
using db = double;
 
// Các hàm hỗ trợ (không dùng class)
 
const int MAXN = 1005;
const int MAXV = 2005;
 
// Mảng toàn cục lưu trữ Đồ thị Suy luận (Implication Graph)
// Chiều đầu tiên [2] dùng để phân biệt: 0 là đồ thị f1, 1 là đồ thị f2
bitset<MAXN> good_bs[2][MAXV], bad_bs[2][MAXV];
bool bv[2][MAXV];
int val[2][MAXN]; // Lưu giá trị gán cho các biến: -1 (chưa gán), 0 (False), 1 (True)
int sof[MAXN];    // Mảng lưu tạm các đỉnh đã thay đổi trạng thái (để rollback nếu sai)
 
// Hàm chuyển đổi đỉnh: 
// Đỉnh dương 1..n -> index 0..(n-1)
// Đỉnh âm -1..-n -> index n..(2n-1)
int get_num(int n_total, int a) {
    if (a > 0) return a - 1;
    return n_total + (-a) - 1;
}
 
// Hàm lấy đỉnh phủ định (negation) của một đỉnh
int get_neg(int n_total, int a) {
    if (a < n_total) return a + n_total;
    return a - n_total;
}
 
// Thêm cung suy luận a -> b vào đồ thị g_idx
void add_implication(int g_idx, int n_total, int a, int b) {
    a = get_num(n_total, a);
    b = get_num(n_total, b);
    if (b < n_total) {
        good_bs[g_idx][a].set(b);
    } else {
        bad_bs[g_idx][a].set(b - n_total);
    }
}
 
// Thêm một mệnh đề (a OR b) vào đồ thị g_idx
// Tương đương với 2 cung suy luận: (!a -> b) và (!b -> a)
void add_clause(int g_idx, int n_total, int a, int b) {
    add_implication(g_idx, n_total, -a, b);
    add_implication(g_idx, n_total, -b, a);
}
 
// Tính bao đóng chuyển tiếp (Transitive Closure) để tìm tất cả các liên kết gián tiếp
void build_transitive_closure(int g_idx, int n_total) {
    // Khởi tạo: Mỗi đỉnh tự suy ra chính nó
    for (int i = 0; i < 2 * n_total; ++i) {
        if (i < n_total) good_bs[g_idx][i].set(i);
        else bad_bs[g_idx][i].set(i - n_total);
    }
    
    // Thuật toán tương tự Floyd-Warshall nhưng tối ưu cực nhanh bằng std::bitset O(N^3 / 64)
    for (int mid = 0; mid < 2 * n_total; ++mid) {
        for (int i = 0; i < 2 * n_total; ++i) {
            bool ok = false;
            if (mid < n_total) {
                if (good_bs[g_idx][i].test(mid)) ok = true;
            } else {
                if (bad_bs[g_idx][i].test(mid - n_total)) ok = true;
            }
            if (ok) {
                good_bs[g_idx][i] |= good_bs[g_idx][mid];
                bad_bs[g_idx][i] |= bad_bs[g_idx][mid];
            }
        }
    }
 
    // Đánh dấu các đỉnh gây ra mâu thuẫn (Ví dụ: A -> !A nghĩa là A buộc phải sai)
    for (int i = 0; i < 2 * n_total; ++i) {
        if (i < n_total) {
            if (bad_bs[g_idx][i].test(i)) bv[g_idx][i] = true;
        } else {
            if (good_bs[g_idx][i].test(i - n_total)) bv[g_idx][i] = true;
        }
    }
 
    // Lan truyền trạng thái mâu thuẫn
    for (int i = 0; i < 2 * n_total; ++i) {
        for (int j = 0; j < 2 * n_total; ++j) {
            if (!bv[g_idx][j]) continue;
            if (j < n_total) {
                if (good_bs[g_idx][i].test(j)) bv[g_idx][i] = true;
            } else {
                if (bad_bs[g_idx][i].test(j - n_total)) bv[g_idx][i] = true;
            }
        }
    }
}
 
// Kiểm tra xem đồ thị có tồn tại nghiệm hợp lệ hay không
bool has_solution(int g_idx, int n_total) {
    for (int i = 0; i < n_total; ++i) {
        // Nếu A buộc phải là False VÀ !A buộc phải là False -> Vô nghiệm
        if (bad_bs[g_idx][i].test(i) && good_bs[g_idx][i + n_total].test(i)) {
            return false;
        }
    }
    return true;
}
 
// Thử gán giá trị cho một đỉnh, trả về true nếu gán thành công không gây mâu thuẫn
bool select_var(int g_idx, int n_total, int v) {
    int t = v;
    if (val[g_idx][v] == 0) t += n_total;
    if (bv[g_idx][t]) return false;
 
    int k = 0;
    sof[k++] = v;
    bool ok = true;
 
    for (int i = 0; i < n_total; ++i) {
        if (good_bs[g_idx][t].test(i)) {
            if (val[g_idx][i] == 0) { ok = false; break; }
            if (val[g_idx][i] == -1) {
                val[g_idx][i] = 1;
                sof[k++] = i;
            }
        }
        if (bad_bs[g_idx][t].test(i)) {
            if (val[g_idx][i] == 1) { ok = false; break; }
            if (val[g_idx][i] == -1) {
                val[g_idx][i] = 0;
                sof[k++] = i;
            }
        }
    }
 
    // Nếu mâu thuẫn, rollback (trả lại trạng thái chưa gán) cho toàn bộ biến vừa thử
    if (!ok) {
        for (int i = 0; i < k; ++i) val[g_idx][sof[i]] = -1;
        return false;
    }
    return true;
}
 
// Hàm in nghiệm và thoát chương trình
void print_solution_and_die(int g_idx, int n_total, int f1, int f2) {
    memset(val[g_idx], -1, sizeof(val[g_idx]));
    
    // Ép gán các giá trị vi phạm để tạo ra nghiệm khác biệt
    if (f1 != -1) {
        if (f1 < n_total) val[g_idx][f1] = 1;
        else {
            f1 -= n_total;
            val[g_idx][f1] = 0;
        }
        select_var(g_idx, n_total, f1);
    }
    if (f2 != -1) {
        int base_f2 = (f2 < n_total) ? f2 : f2 - n_total;
        if (val[g_idx][base_f2] == -1) {
            if (f2 < n_total) val[g_idx][f2] = 1;
            else {
                f2 -= n_total;
                val[g_idx][f2] = 0;
            }
            select_var(g_idx, n_total, f2);
        }
    }
    
    // Gán các biến còn lại một cách tham lam
    for (int i = 0; i < n_total; ++i) {
        if (val[g_idx][i] != -1) continue;
        val[g_idx][i] = 0;
        if (!select_var(g_idx, n_total, i)) {
            val[g_idx][i] = 1;
            select_var(g_idx, n_total, i);
        }
    }
    
    // In kết quả
    for (int i = 0; i < n_total; ++i) {
        if (i > 0) cout << " ";
        cout << val[g_idx][i];
    }
    cout << "\n";
    exit(0);
}
 
// Kiểm tra xem việc gán v1 và v2 có sinh ra mâu thuẫn ngay lập tức không
bool check_bad(int g_idx, int v1, int v2) {
    auto t = (good_bs[g_idx][v1] | good_bs[g_idx][v2]) & (bad_bs[g_idx][v1] | bad_bs[g_idx][v2]);
    return t.any();
}
 
bool used_pair[MAXV][MAXV];
 
// Hàm đối chiếu: Dò xem các mệnh đề của đồ thị `from` có bị đồ thị `to` vi phạm không
void check_conflict(int g_idx_from, int g_idx_to, int n_total, const vector<pair<int, int>>& clauses) {
    memset(used_pair, 0, sizeof(used_pair));
    for (const auto& cl : clauses) {
        int v1 = get_num(n_total, cl.first);
        int v2 = get_num(n_total, cl.second);
        
        // Phủ định lại mệnh đề để ép tạo lỗi (A OR B sai nghĩa là !A và !B phải đúng)
        v1 = get_neg(n_total, v1);
        v2 = get_neg(n_total, v2);
        if (v1 > v2) swap(v1, v2);
        
        if (used_pair[v1][v2]) continue;
        used_pair[v1][v2] = true;
 
        if (bv[g_idx_to][v1] || bv[g_idx_to][v2]) continue;
        if (check_bad(g_idx_to, v1, v2)) continue;
        
        // Nếu đồ thị to có thể chấp nhận cấu hình phủ định này -> 2 đồ thị khác biệt
        print_solution_and_die(g_idx_to, n_total, v1, v2);
    }
}
 
void solve() {
    int n, m1, m2;
    if (!(cin >> n >> m1 >> m2)) return;
    
    // Clear dữ liệu toàn cục nếu có nhiều test (mặc dù bài này test=1)
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 2 * n; ++j) {
            good_bs[i][j].reset();
            bad_bs[i][j].reset();
            bv[i][j] = false;
        }
    }
    
    vector<pair<int, int>> cl1(m1), cl2(m2);
    for (int i = 0; i < m1; ++i) {
        cin >> cl1[i].first >> cl1[i].second;
        add_clause(0, n, cl1[i].first, cl1[i].second);
    }
    for (int i = 0; i < m2; ++i) {
        cin >> cl2[i].first >> cl2[i].second;
        add_clause(1, n, cl2[i].first, cl2[i].second);
    }
    
    // Khởi tạo Transitive Closure cho cả 2 đồ thị
    build_transitive_closure(0, n);
    build_transitive_closure(1, n);
    
    bool r1 = has_solution(0, n);
    bool r2 = has_solution(1, n);
    
    // Nếu cả 2 đều vô nghiệm -> Cùng tập nghiệm (Tập rỗng)
    if (!r1 && !r2) {
        cout << "SIMILAR\n";
        return;
    }
    
    // Nếu 1 cái có nghiệm, 1 cái vô nghiệm -> In ngay nghiệm của cái hợp lệ
    if (!r1) print_solution_and_die(1, n, -1, -1);
    if (!r2) print_solution_and_die(0, n, -1, -1);
    
    // Đối chiếu xem 2 đồ thị có chứa nghiệm vi phạm lẫn nhau không
    check_conflict(0, 1, n, cl1);
    check_conflict(1, 0, n, cl2);
    
    // Nếu mọi đối chiếu đều không tìm được lỗ hổng -> Hoàn toàn tương đồng
    cout << "SIMILAR\n";
}
 
__Yang_Yang__()
{
    ios_base::sync_with_stdio(0);
    cin.tie(nullptr); cout.tie(nullptr);
    // Nếu bài toán không có test case, hãy cmt dòng cin>>test; lại
    // cin >> test;
    while(test--) solve();
    return 0;
}
 
/*
TRẢ LỜI CÁC CÂU HỎI:
 
Cấu trúc dữ liệu nào dùng trong code, và nhiệm vụ của chúng?
=> std::bitset: Được sử dụng để lưu trữ các đồ thị kề/cung suy luận (good_bs, bad_bs). Bitset giúp cực kỳ tối ưu hóa các phép toán logic trên bit, tăng tốc độ xử lý mảng bool gấp 64 lần so với duyệt thông thường.
=> Mảng đa chiều toàn cục: Giúp lưu riêng biệt trạng thái của 2 công thức `f1` (index 0) và `f2` (index 1) mà không cần tạo class hay struct có phương thức, đáp ứng đúng giới hạn của template.
 
Thuật toán nào dùng trong code, và nhiệm vụ của chúng?
=> Transitive Closure (Bao đóng chuyển tiếp) qua thuật toán Floyd-Warshall: Dùng để phát hiện tất cả các hậu quả suy luận lô-gic gián tiếp. Ví dụ A suy ra B, B suy ra C thì thuật toán đảm bảo tìm được cung nối trực tiếp A -> C.
=> Backtracking Tham lam (Gán biến): Để giải quyết và dò nghiệm cho bài toán 2-SAT (hàm `select_var`).
 
Công thức toán học nào hoặc công thức truy hồi nào dùng để làm gì?
=> Logic Mệnh đề: Công thức phân rã của 2-SAT là `(A OR B) <=> (!A -> B) AND (!B -> A)`. Dựa vào đây để vẽ đồ thị kề có hướng suy luận.
 
Độ phức tạp thuật toán là bao nhiêu?
=> Thời gian: Nặng nhất nằm ở bước chạy Transitive Closure với độ phức tạp $O(N^3 / 64)$. Với N tối đa = 1000, số vòng lặp tính ra chỉ tốn chưa tới $1.25 \times 10^8$ phép tính bit, dễ dàng pass giới hạn thời gian 3.0 giây.
=> Không gian bộ nhớ: Các mảng bitset tiêu tốn $O(N^2)$ bit bộ nhớ. Tính ra chưa tốn tới 5MB, hoàn toàn thỏa mãn 256MB.
 
Dùng đến kỹ thuật thiết kế bài toán nào?
=> Đồ thị kề có hướng (Directed Implication Graph): Biểu diễn quan hệ đúng/sai của 2-SAT qua đồ thị.
=> Tối ưu hóa Bitmask: Nén ma trận kề boolean về bitset để đạt Time Complexity tối ưu thực tế.
 
Độ khó của bài toán khoảng bao nhiều trên thang 10?
=> 8.5/10. Kiến thức 2-SAT không phải là quá hiếm ở CP, nhưng ở bài này yêu cầu so sánh sự bất đồng của tận 2 đồ thị nên việc tạo ra trạng thái đối chiếu (phủ định các clauses của `f1` để ép vào `f2`) khá phức tạp và hack não.
*/