2-sat-總結+例題

http://www.javashuo.com/article/p-zsbohtnr-e.htmlphp

從寒假就開始準備學2sat,,而後當時瞭解了一下模板就溜了,,,一直到上個星期,,三月底纔好好從新的看一下,,,作了一些題,,感受大體的瞭解了2sat的基本套路,,通常的題都是在建圖作文章,,這題出現的頻率貌似也不高,,,以後就放一放吧,,,啥時候忘記了就翻出來看一看,,嘿嘿html

概述

2-sat是k-sat問題中k==2時的一種狀況,,(廢話qaq,,node

當k大於等於3時是npc問題,,因此通常都是問的2-sat,,ios

這種題的大概形式是: 對於給定的n對的點,要求每一對都只能選擇一個,而且其中還有一些限制條件,好比說選了u就不能選擇v等等,,c++

而後問你有沒有可行解,,,算法

解決這類問題通常是用 染色法(求字典序最小的解)強連通份量法(拓撲排序只能獲得任意解),,數組

算法分析

  • 首先要明白一個道理:對於 u->v(選擇u就不能選擇v)這樣的限制條件能夠用它的逆否命題來轉換爲:u->v'(選擇u就必須選v')以及 v->u'(選擇v就必須選u')
  • 最後的建出的圖是對稱的,,
  • 具體的數學證實和算法推導看這裏kuangbin的博客,,多看幾遍,,跟着敲一遍代碼後再看看就差很少懂了

染色法(求字典序最小的解)

這個算法的大體思路就是遍歷每一對點的兩種狀況:選p或者選p',,,安全

而後一直從p的下一個嘗試下去,,中間如果碰到不能避免的不知足題意的選擇時,證實這條路下來的嘗試時不行的,,從新選擇,,一直下去。。。也就是一個深搜的過程,,時間複雜度大概是 \(O(nm)\),,函數

能夠看看這篇博客,,工具

以及這個

還有這個裏的那幾個模型很好

模型一:二者(A,B)不能同時取
  那麼選擇了A就只能選擇B’,選擇了B就只能選擇A’
  連邊A→B’,B→A’

模型二:二者(A,B)不能同時不取
  那麼選擇了A’就只能選擇B,選擇了B’就只能選擇A
  連邊A’→B,B’→A

模型三:二者(A,B)要麼都取,要麼都不取
  那麼選擇了A,就只能選擇B,選擇了B就只能選擇A,選擇了A’就只能選擇B’,選擇了B’就只能選擇A’
  連邊A→B,B→A,A’→B’,B’→A’

模型四:二者(A,A’)必取A
  那麼,那麼,該怎麼說呢?先說連邊吧。
  連邊A’→A

強連通份量法(拓撲排序只能獲得任意解)

這個算法的流程爲:

  • 建圖
  • 求極大聯通份量(子圖)
  • 縮點,轉化成DAG(有向無環圖)
  • 判斷有無解
  • 新圖拓撲排序
  • 自底向上選擇、刪除
  • 輸出

時間複雜度大概爲 \(O(m)\),,就是難寫,,並且不能輸出字典序小的解,,,

例題和模板

hdu-1814

這道模板題,,讓輸出的書字典序小的解,,,只能用第一種方法了,,,

題意就是一個國家有不少黨派,,每一個黨派只有兩我的,,如今要從這一堆黨派中每一個黨派選擇一我的參加會議,,其中一些人之間有分歧,,即p去了q就不去這樣的限制條件,,問你是否能找出知足題意全部限制條件的選擇方法,,,有解的話就輸出字典序最小的解,,

題意和上面那個百度文庫的例題同樣,,,

//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <vector>
#include <queue>
#include <functional>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e5 + 5;
const int maxm = 2e5 + 5;
const int mod = 1e9 + 7;
inline ll read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

//2sat_kuangbin
struct edge
{
    int to, next;
}edge[maxn];
int head[maxn], tot;
void init()
{
    tot = 0;
    memset(head, -1, sizeof head);
}
void addedge(int u, int v)
{
    edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++;
}
bool vis[maxn];
int s[maxn], top;
bool dfs(int u)
{
    if(vis[u^1])return false;       //若是這個點p的對立面p'選了,那麼這個點就不選
    if(vis[u])  return true;        //若是這個點已經選了,就不從這個點繼續向下找了
    vis[u] = true;                  //這個點p沒選而且對立面p'沒選的狀況下,選擇這個點,而且嘗試從這個點尋找可能的解法
    s[top++] = u;                   //把這個可能的一種狀況壓棧,保存
    for(int i = head[u]; ~i; i = edge[i].next)
        if(!dfs(edge[i].to))
            return false;           //嘗試全部與點u相連的點v,若是從點v出發的嘗試不可行時不選
    return true;
}
bool two_sat(int n)
{
    memset(vis, false, sizeof vis); //vis[i]標記那些點要選
    for(int i = 0; i < n; i += 2)
    {
        if(vis[i] || vis[i^1])continue;//若是這一對點有一個選過就嘗試下一對的點
        top = 0;
        if(!dfs(i))                 //若是從點i出發的嘗試不行,就將棧中全部這條可能的路徑上的點標記爲未選
        {
            while(top)vis[s[--top]] = false;
            if(!dfs(i^1))return false;//若是點i的對立面i'都不行的話,證實沒法找到這樣一條可行解,使得每一對點僅選擇一個而且知足對應的限制
        }
    }
    return true;
}
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    int n, m, u, v;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        init();
        for(int i = 1; i <= m; ++i)
        {
            scanf("%d%d", &u, &v);
            --u;--v;        //點的編號從0開始,方便使用p^1來表示p的對立面
            addedge(u, v^1);//建圖,限制條件u->v(選擇u就不能選擇v)等價於u->v' && v->u' (選擇u必須選額v' 和 選擇v就必須選擇u')
            addedge(v, u^1);
        }
        if(two_sat(2 * n))  //存在解時
        {
            for(int i = 0; i < 2 * n; ++i)
                if(vis[i])  //將最後字典序最小的可行解輸出
                    printf("%d\n", i + 1);
        }
        else
            printf("NIE\n");
    }
    return 0;
}

uva-3211

這道題的題意是: 每架飛機有兩個降落的時間點(a, b),而後對於每兩架飛機之間定義一個安全的時間間隔x,,問你在保證所有飛機都安全降落的狀況下,最小的時間間隔x中的最大值,,,

先無論x怎麼求,,假設如今已知一個x,,問你任意兩架飛機之間的時間間隔最小是x時可不能夠,,

由於對於一架飛機來講,有兩個時間點,但只能選擇一個,也就是說a, b 是相互排斥的,,就像上面那道題中的每一個黨派中只選擇一我的同樣,,同時對於每兩架飛機之間,他們選擇的降落時間的差的絕對值應該是至少大於x的,,,這樣就能看出一個限制條件,,即:對於第i架飛機選擇的降落時間 \(a_i\) 與第j架飛機選擇的降落時間 \(a_j\) 之間知足 \(abs(a_i - a_j) \geq x\),,咱們能夠遍歷每一架飛機和它後面的飛機所選擇的降落時間的結果,,對於不知足條件的就能夠認爲是 選擇了第i架的一個降落時間點就不能選第j架的一個降落的時間點 ,,,這就是最後提取出來的限制條件,,也就是 選擇了第i架的一個時間點就必須選第j架的另外一個時間點選擇了第j架的一個時間點就必須選第i架飛機的另外一個時間點 ,,,(這裏有一個問題: 貌似沒有保證 選擇了第i架飛機的一個時間點就不選第j架飛機的一個時間點時 ,選擇第j架飛機的另外一個時間點就也知足相差不小於x,,,可是我找到的博客沒有一個說這個的,不考慮也能過,,固然也有可能我理解錯了,,有的話,求指正,,作了下面那道題以後瞬間明白了,,,建圖的時候那些時間點之間都枚舉判斷了,,不是一對一對的枚舉,,,233)

抽象一下就是:首先咱們用一個數組保存全部的時間點 \(a_k\),下標從0開始到 \(2 *n-1\) ,其中 \(a_{2*k}, a_{2*k+1}\) 表示第k架飛機的兩個時間,,咱們遍歷每一架飛機, 若是知足 \(abs(a_{第i架飛機的兩個時間} - a_{第j架飛機的兩個時間}) \geq x\) 至關因而 \(p_i->q_j\) 這樣的限制關係,,那麼建邊就是 \(p_i->q_j'\) 以及 \(q_j->p_i'\) ,,而後跑2sat判斷是否有解,,

而後看x的求法,,

最大xx中的最小值 或 最小xx中的最大值 通常都是用二分來枚舉這個值,而後判斷是否知足必定條件

咱們能夠枚舉x,,而後用這個x來建圖跑一下判斷是否可行來求出其最大值,,

參考

我這種寫法貌似時間複雜度很很差,,6s左右,,emmm不知道那裏寫崩了,,多是那個存數據的vector的鍋,,,

#include <bits/stdc++.h>
// #include <iostream>
// #include <cstdio>
// #include <cstdlib>
// #include <string.h>
// #include <vector>
// #include <queue>
#include <stack>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e4 + 5;
const int maxm = 2e7 + 5;
const int mod = 1e9 + 7;

struct edge
{
    int to, next;
}edge[maxm];
int tot, head[maxm];
void init()
{
    tot = 0;
    memset(head, -1, sizeof head);
}
void addedge(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

bool vis[maxn];
int sta[maxn], top;
bool dfs(int u)
{
    if(vis[u ^ 1])return false;
    if(vis[u])return true;
    vis[u] = true;
    sta[++top] = u;
    for(int i = head[u]; ~i; i = edge[i].next)
        if(!dfs(edge[i].to))
            return false;
    return true;
}
bool twosat(int n)
{
    memset(vis, false, sizeof vis);
    for(int i = 0; i < n; i += 2)
    {
        if(vis[i] || vis[i ^ 1]) continue;
        top = -1;
        if(!dfs(i))
        {
            while(~top)vis[sta[top--]] = false;
            if(!dfs(i ^ 1))return false;
        }
    }
    return true;
}
int n;
vector<pair<int, int> > e;
int abss(int x){return x < 0 ? -x : x;}
bool check(int x)
{
    init();
    for(int i = 0; i < e.size(); ++i)
    {
        for(int j = i + 1; j < e.size(); ++j)
        {
            if(abss(e[i].first - e[j].first) < x)
                addedge(i << 1, j << 1 | 1), addedge(j << 1, i << 1 | 1);
            if(abss(e[i].first - e[j].second) < x)
                addedge(i << 1, j << 1), addedge(j << 1 | 1, i << 1 | 1);
            if(abss(e[i].second - e[j].first) < x)
                addedge(i << 1 | 1, j << 1 | 1), addedge(j << 1, i << 1);
            if(abss(e[i].second - e[j].second) < x)
                addedge(i << 1 | 1, j << 1), addedge(j << 1 | 1, i << 1);
        }
    }
    if(twosat(2 * n))   return true;
    else                return false;
}
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
    // ios_base::sync_with_stdio(0);
    // cin.tie(0);cout.tie(0);
    while(~scanf("%d", &n))
    {
        int u, v;
        vector<pair<int, int> >().swap(e);
        e.clear();
        for(int i = 1; i <= n; ++i)
        {
            scanf("%d%d", &u, &v);
            e.push_back(make_pair(u, v));
        }
        int l = 0, r = 10000001;
        int ans;
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            if(check(mid))l = mid + 1, ans = mid;
            else    r = mid - 1;
        }
        printf("%d\n", ans);
    }
    return 0;    
}

HDU-3622-Bomb Game

這道題和上面那道題差很少,也是二分枚舉+建圖判斷可行性,,爲了精度能夠先不開方直接枚舉 \((2*r)^2\) 的值,,,

題意就是n對能夠放置炸彈的點,,選擇沒對點中的一個,同時能夠爲每一個炸彈設置一個爆炸範圍r,,可是每個炸彈的爆炸範圍不能波及到其餘的點,,求一個最大的r,,,

由於和上面那題差很少,,直接看代碼吧,,,

// #include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <vector>
#include <cmath>
// #include <queue>
#include <stack>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567 > 1e10
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e5 + 5;
const int maxm = 2e7 + 5;
const int mod = 1e9 + 7;

struct edge
{
    int to, next;
}edge[maxn];
int tot, head[maxn];
void init()
{
    tot = 0;
    memset(head, -1, sizeof head);
}
void addedge(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
bool vis[maxn];
int sta[maxn], top;
bool dfs(int u)
{
    if(vis[u ^ 1])return false;
    if(vis[u])return true;
    vis[u] = true;
    sta[++top] = u;
    for(int i = head[u]; ~i; i = edge[i].next)
        if(!dfs(edge[i].to))
            return false;
    return true;
}
bool twosat(int n)
{
    memset(vis, false, sizeof vis);
    for(int i = 0; i < n; i += 2)
    {
        if(vis[i] || vis[i ^ 1])continue;
        top = -1;
        if(!dfs(i))
        {
            while(~top)vis[sta[top--]] = false;
            if(!dfs(i ^ 1))return false;
        }
    }
    return true;
}
#define x first
#define y second
vector<pair<int, int> > c;
int n;
inline int dis(int i, int j)
{
    return (c[i].x - c[j].x) * (c[i].x - c[j].x) + (c[i].y - c[j].y) * (c[i].y - c[j].y);
}
bool check(int x)
{
    init();
    memset(vis, false, sizeof false);
    for(int i = 0; i < 2 * n; ++i)
        for(int j = i + 1; j < 2 * n; ++j)
            if(dis(i, j) < x)
                addedge(i, j ^ 1), addedge(j, i ^ 1);
    if(twosat(2 * n))return true;
    return false;
}
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
    // ios_base::sync_with_stdio(0);
    // cin.tie(0);cout.tie(0);
    // int k = 2e9;
    // cout << k << endl;
    while(~scanf("%d", &n))
    {
        int x, y;
        c.clear();
        for(int i = 1; i <= n; ++i)
        {
            scanf("%d%d", &x, &y);
            c.push_back(make_pair(x, y));
            scanf("%d%d", &x, &y);
            c.push_back(make_pair(x, y));
        }
        int l = 0, r = inf;
        while(l < r)
        {
            if(l + 1 == r)break;
            int mid = (l + r) >> 1;
            if(check(mid))l = mid;
            else r = mid;
        }
        double ans = sqrt(l) / 2.0;
        printf("%.2f\n", ans);
    }
    return 0;    
}

HDU-4115

這道題是利用2sat判斷是否有解,,,

題意就是兩人玩石頭剪刀布,一共玩n輪,,其中一我的 優吉歐 bob的出手狀況給你,,

而後對於另外一我的愛麗絲她有一些每輪之間出手的限制狀況,k==0時表示a輪與b輪的出手要一致,爲1時表示出手要不同,,問你愛麗絲有沒有贏的狀況,,,

這種題通常都是建圖麻煩一些,,反而2sat算法自己不會有大的改變,當函數調用就好了,,,

我一開始想着,p==q的限制條件可不能夠直接連一條p->q的邊,不等就像以前那樣用逆否建兩條邊,,,而後一直有問題,,,後來看了別人的方法發現別人都是在k==0時找不等的,而後建兩條邊,等於一的時候找相等的,建兩條邊,,,,就是去找全部的 矛盾項 ,,emmm

後來我總以爲能夠直接利用都選的條件建圖,,,最後找到一個博客(12年的,,,),,裏面提到能夠用必選項來建圖,,就是最後的建圖有些麻煩,,好費勁吶,,這種建圖的方法大概是:對於要求相等的兩組,找到相等的出手的話,就建一條雙向邊,由於一組裏確定不一樣,因此只需判斷其中一個,,若是沒有就連到它的對立面,,對於不要求相等的兩組,,就找矛盾的條件建兩條邊,,,(其實仍是直接找矛盾項建圖方便一些,,,,,

最後注意一下細節,,,a了一次以後嘗試改的好看一些,而後在細細碎碎的地方wa了幾回

矛盾項

// #include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <vector>
#include <cmath>
// #include <queue>
#include <stack>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567 > 1e10
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e5 + 5;
const int maxm = 2e7 + 5;
const int mod = 1e9 + 7;

struct edge
{
    int to, next;
}edge[maxn];
int tot, head[maxn];
void init()
{
    tot = 0;
    memset(head, -1, sizeof head);
}
void addedge(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
bool vis[maxn];
int sta[maxn], top;
bool dfs(int u)
{
    if(vis[u ^ 1])return false;
    if(vis[u])return true;
    vis[u] = true;
    sta[++top] = u;
    for(int i = head[u]; ~i; i = edge[i].next)
        if(!dfs(edge[i].to))
            return false;
    return true;
}
bool twosat(int n)
{
    memset(vis, false, sizeof vis);
    for(int i = 0; i < n; i += 2)
    {
        if(vis[i] || vis[i ^ 1])continue;
        top = -1;
        if(!dfs(i))
        {
            while(~top)vis[sta[top--]] = false;
            if(!dfs(i ^ 1))return false;
        }
    }
    return true;
}
struct node
{
    int a, b, c;
}node[maxn];
int n, m;
int a[maxn], b[maxn];
inline void f(int i, bool cnt)
{
    if(((bool)(a[node[i].a] - a[node[i].b])) == cnt)addedge(node[i].a, node[i].b ^ 1), addedge(node[i].b, node[i].a ^ 1);
    if(((bool)(a[node[i].a] - a[node[i].b ^ 1])) == cnt)addedge(node[i].a, node[i].b), addedge(node[i].b ^ 1, node[i].a ^ 1);
    if(((bool)(a[node[i].a ^ 1] - a[node[i].b])) == cnt)addedge(node[i].a ^ 1, node[i].b ^ 1), addedge(node[i].b, node[i].a);
    if(((bool)(a[node[i].a ^ 1] - a[node[i].b ^ 1])) == cnt)addedge(node[i].a ^ 1, node[i].b), addedge(node[i].b ^ 1, node[i].a);
}
bool solve()
{
    init();
    //~~這樣寫還會變慢,,,,,emmmm~~換成inline就好了,,,,
    // for(int i = 1; i <= m; ++i)
    // {
    //     if(node[i].c)
    //         f(i, false);
    //     else    
    //         f(i, true);
    // }
    for(int i = 1; i <= m; ++i)
    {
        if(node[i].c)
        {
            if(a[node[i].a] == a[node[i].b])addedge(node[i].a, node[i].b ^ 1), addedge(node[i].b, node[i].a ^ 1);
            if(a[node[i].a] == a[node[i].b ^ 1])addedge(node[i].a, node[i].b), addedge(node[i].b ^ 1, node[i].a ^ 1);///////////////
            if(a[node[i].a ^ 1] == a[node[i].b])addedge(node[i].a ^ 1, node[i].b ^ 1), addedge(node[i].b, node[i].a);
            if(a[node[i].a ^ 1] == a[node[i].b ^ 1])addedge(node[i].a ^ 1, node[i].b), addedge(node[i].b ^ 1, node[i].a);
            // f(i, true);
        }
        else
        {
            if(a[node[i].a] != a[node[i].b])addedge(node[i].a, node[i].b ^ 1), addedge(node[i].b, node[i].a ^ 1);
            if(a[node[i].a] != a[node[i].b ^ 1])addedge(node[i].a, node[i].b), addedge(node[i].b ^ 1, node[i].a ^ 1);
            if(a[node[i].a ^ 1] != a[node[i].b])addedge(node[i].a ^ 1, node[i].b ^ 1), addedge(node[i].b, node[i].a);
            if(a[node[i].a ^ 1] != a[node[i].b ^ 1])addedge(node[i].a ^ 1, node[i].b), addedge(node[i].b ^ 1, node[i].a);
            
            // f(i, false);
        }
    }
    if(twosat(n * 2))return true;
    return false;
}

// bool solve()
// {
//     init();
//     for(int i = 1; i <= m; ++i)
//     {
//         if(node[i].c)
//         {
//             if(a[node[i].a] == a[node[i].b])addedge(node[i].a, node[i].b ^ 1), addedge(node[i].b, node[i].a ^ 1);
//             else if(a[node[i].a] == a[node[i].b ^ 1])addedge(node[i].a, node[i].b), addedge(node[i].b ^ 1, node[i].a ^ 1);
//             if(a[node[i].a ^ 1] == a[node[i].b])addedge(node[i].a ^ 1, node[i].b ^ 1), addedge(node[i].b, node[i].a);
//             else if(a[node[i].a ^ 1] == a[node[i].b ^ 1])addedge(node[i].a ^ 1, node[i].b), addedge(node[i].b ^ 1, node[i].a);
//         }
//         else
//         {
//             if(a[node[i].a] == a[node[i].b])addedge(node[i].a, node[i].b), addedge(node[i].b, node[i].a);
//             else if(a[node[i].a] == a[node[i].b ^ 1])addedge(node[i].a, node[i].b ^ 1), addedge(node[i].b ^ 1, node[i].a);
//             else addedge(node[i].a, node[i].a ^ 1);

//             if(a[node[i].a ^ 1] == a[node[i].b])addedge(node[i].a ^ 1, node[i].b), addedge(node[i].b, node[i].a ^ 1);
//             else if(a[node[i].a ^ 1] == a[node[i].b ^ 1])addedge(node[i].a ^ 1, node[i].b ^ 1), addedge(node[i].b ^ 1, node[i].a ^ 1);
//             else addedge(node[i].a ^ 1, node[i].a);

//             if(a[node[i].b] != a[node[i].a] && a[node[i].b] != a[node[i].a ^ 1])addedge(node[i].b, node[i].b ^ 1);
//             if(a[node[i].b ^ 1] != a[node[i].a] && a[node[i].b ^ 1] != a[node[i].a ^ 1])addedge(node[i].b ^ 1, node[i].b);
//         }
//     }
//     if(twosat(n * 2))return true;
//     return false;
// }
int getb(int x)
{
    if(x == 1)return 2;
    if(x == 2)return 3;
    if(x == 3)return 1;
}
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
    // ios_base::sync_with_stdio(0);
    // cin.tie(0);cout.tie(0);
    int t;scanf("%d", &t);
    int cnt = 1;
    while(t--)
    {
        scanf("%d%d", &n, &m);
        for(int i = 0; i < n; ++i)scanf("%d", &a[i << 1]);
        for(int i = 1; i <= m; ++i)
        {
            scanf("%d%d%d", &node[i].a, &node[i].b, &node[i].c);
            --node[i].a;--node[i].b;
            node[i].a <<= 1;node[i].b <<= 1;
        }
        for(int i = 0; i < n; ++i)
            a[i << 1 | 1] = getb(a[i << 1]);
        if(solve())printf("Case #%d: yes\n", cnt++);
        else       printf("Case #%d: no\n", cnt++);
    }
    return 0;    
}

poj-3678-Katu Puzzle

一道經典題,,

題意就是求一個01序列是否有解,其中一些位置間的關係限定了,,,

重點是建圖:

首先定義選a爲0,選a'爲1,那麼:

  • \(a \ AND \ b = 1\): 表示ab都必須爲1,,因此 \(a = 0\) 的時候就要讓其矛盾也就是指到 \(a = 1\),也就是 a->a',同理b也是,b->b';
  • \(a \ AND \ b = 0\): 表示ab中至少一個爲零,因此 當 \(a=0\) 時必定成立,不用管,當 \(a=1\)時b必須爲0,也就是 a'->b;同理對於b也是如此,,b'-a;
  • \(a \ OR \ b = 1\): 表示ab中至少一個爲1,因此當 \(a=0\) 的時候b必定爲1, 加邊 a->b',,\(a=1\) 的時候已經爲1不用管,同理對於b來講就是加邊 b->a';
  • \(a \ OR \ b = 0\): 表示ab都必須爲零,和最上面那個同樣,\(a=1\) 的時候必定不成立,因此要讓它矛盾,加邊 a'->a, 同理加邊 ``b'->b
  • \(a \ XOR \ b = 1\): 表示ab不一樣,四種狀況: \(a=0,b=1\): a->b'; \(a=1,b=0\): a'->b; \(b=0,a=1\): b->a'; \(b=1,a=0\): b'->a;
  • \(a \ XOR \ b = 0\): 和上面相反的四種狀況:\(a=0,b=0\): a->b; \(a=1,b=1\): a'->b'; \(b=0,a=0\): b->a; \(b=1,a=1\): b'->a';

按照上面的建圖就好了,,

參考1

以前作題都是找矛盾邊,,但這道題找矛盾邊很麻煩,,直接找知足題意的邊就好了,,(貌似要保證時對稱的圖???

同時像 \(a=1,b=1\) 這種都選項能夠拆分紅兩個來作:\(a \bigwedge b=1 \implies (a \bigvee a) \bigwedge (b \bigvee b) = 1\) 而後就能夠拆成; \(a \bigvee a = 1 , b \bigvee b = 1\),(a爲1,b爲1),, 而後建邊 a->a'b->b'

參考2

參考3

這個貌似是用的矛盾項+必選項作的

// #include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <vector>
#include <cmath>
// #include <queue>
// #include <stack>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567 > 1e10
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e5 + 5;
const int maxm = 2e7 + 5;
const int mod = 1e9 + 7;

struct edge
{
    int to, next;
}edge[maxn];
int tot, head[maxn];
void init()
{
    tot = 0;
    memset(head, -1, sizeof head);
}
void addedge(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
    //cout << u << "->" << v << endl;
}
bool vis[maxn];
int sta[maxn], top;
bool dfs(int u)
{
    if(vis[u ^ 1])return false;
    if(vis[u])return true;
    vis[u] = true;
    sta[++top] = u;
    for(int i = head[u]; ~i; i = edge[i].next)
        if(!dfs(edge[i].to))
            return false;
    return true;
}
bool twosat(int n)
{
    memset(vis, false, sizeof vis);
    for(int i = 0; i < n; i += 2)
    {
        if(vis[i] || vis[i ^ 1])continue;
        top = -1;
        if(!dfs(i))
        {
            while(~top)vis[sta[top--]] = false;
            if(!dfs(i ^ 1))return false;
        }
    }
    return true;
}

int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
    // ios_base::sync_with_stdio(0);
    // cin.tie(0);cout.tie(0);
    int n, m;
    while(~scanf("%d%d", &n, &m))
    {
        int a, b, c;
        char s[10];
        init();
        for(int i = 1; i <= m; ++i)
        {
            scanf("%d%d%d%s", &a, &b, &c, s);
            if(s[0] == 'A')
            {
                if(c)
                {
                    addedge(a << 1, a << 1 ^ 1);    //a^b=1 => (a ! a) ^ (b ! b) => a -> a', b -> b'
                    addedge(b << 1, b << 1 ^ 1);
                }
                else
                {
                    addedge(a << 1 ^ 1, b << 1);    //a^b=0 => a -> b'(01), b -> a'(10)
                    addedge(b << 1 ^ 1, a << 1);
                }
            }
            else if(s[0] == 'O')
            {
                if(c)
                {
                    addedge(a << 1, b << 1 ^ 1);
                    addedge(b << 1, a << 1 ^ 1);
                }
                else
                {
                    addedge(a << 1 ^ 1, a << 1);
                    addedge(b << 1 ^ 1, b << 1);
                }
            }
            else
            {
                if(c)
                {
                    addedge(a << 1, b << 1 ^ 1);
                    addedge(a << 1 ^ 1, b << 1);
                    addedge(b << 1 ^ 1, a << 1);
                    addedge(b << 1, a << 1 ^ 1);
                }
                else
                {
                    addedge(a << 1 ^ 1, b << 1 ^ 1);
                    addedge(b << 1 ^ 1, a << 1 ^ 1);
                    addedge(a << 1, b << 1);
                    addedge(b << 1, a << 1);
                }
            }
        }
        
        if(twosat(2 * n))puts("YES");
        else             puts("NO");
    }
    return 0;    
}

處女座與寶藏

一切的原由,很早以前就據說過2sat,而後在寒假的那次牛客上碰到了這題,,而後想着要學會2sat,,,發現那時我 tarjan 沒學過,,因而跑去學 tarjan ,,而後就一直拖拖到了上個星期,才從新撿起來看,,,

這道題和上面那幾題相比,最大的不一樣是 建圖 ,,這道題2sat只是一個輔助的判斷工具,可是建圖的方法題目裏沒有明說,得本身去想出來其中的關係。。

題意: 有這麼n個寶藏,還有m個開關,按下開關,對應控制的寶藏的狀態就會改變,而後問你是否有寶藏全開的解,,每一個開關會控制k個寶箱,,

最後至關因而求一個開關的選擇序列(好比說0是不按,1是表示按下) ,,因此最後寶箱的起始狀態只是用來讓咱們限制開關選擇的限制條件,大體的思想就是:若是起始狀態是打開的,對應開關的選擇就是 按下->不按 表示選擇不按的狀況;相反的就是 不按->按下 ;

基於這個思想,咱們要預處理一下開關,,由於題目是給的第i個開關控制的k個寶藏,而咱們建圖的時候是要 根據第i個寶藏被一些開關控制的狀況 來得出限制條件。

最後說一下建圖的方法:
若是用 \(u_i\) 表示第i個開關的選擇不按的狀況,\(u_i+1\) 就表示第i個開關選擇按下的狀況,那麼:

  • 對於沒有開關控制的寶箱,若是起始狀態是打開的,那麼這個寶箱就無論了,,但若是是關閉的,由於沒有開關會控制它,因此它不管如何都是關閉的,此時是無解的;
  • 對於只有一個開關控制的寶箱,若是起始狀態是打開的,那我必須選擇對應的開關爲 不按 的狀況,也就是 u^1->u; 同理對立的狀況就是: u->u^1;
  • 對於兩個開關控制的寶箱,若是起始狀態是打開的,和上面同樣,我必須保證開關的變更以後仍是打開的,因此一共有2種狀況:兩個都不按,兩個都按下,一共是建4條邊: u->v , v->u , u^1->v^1 , v^1->u^1; 同理能夠得出對立面就要保證要按下一個開關,一個按下,另外一個就不能按下: u->v^1 , u^1->v , v->u^1 , v^1->u;

最後說一下(好像剛剛說過這是最後一個了哎??!!)這道題的坑點,,

  • 首先是預處理的時候要統一好你的下標,有的人習慣從1開始(貌似其餘人不少都是這麼搞得),,這樣的話對於沒有開關控制的寶箱要建兩條初始邊,,要是從0開始的話(好比我)就要在讀入每一個開關控制的k個寶箱的編號的時候減一;
  • 這道題是單組測試,原本說用多組讀入到文件末應該是沒問題的,可是我一直卡在最後一組上,,wa了十幾發,一度懷疑是代碼寫錯,,該到最後沒得改了,就把 while(~scanf("%d%d", &n, &m)) 去了就A了,,換回一開始wa的也這樣改了也是A了,,,謎通常的操做,,
  • 還有就是題目說是保證每一個寶藏最多被兩個開關控制,,因此建圖的時候判斷開關的數量時就直接 if(balabala){...}else if(balabala){...}}else{...} 最後一個就沒寫判斷,,按照通常的想法應該是沒問題的,,可是這裏最後不寫成 else if(balabala){} 也會卡最後一個測試用例,,,迷<<1,,,

代碼:

染色法

#include <bits/stdc++.h>
// #include <iostream>
// #include <cstdio>
// #include <cstdlib>
// #include <string.h>
// #include <vector>
// #include <cmath>
// #include <queue>
// #include <stack>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567 > 1e10
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e6 + 5;
const int maxm = 2e7 + 5;
const int mod = 1e9 + 7;
  
struct edge
{
    int to, next;
}edge[maxn];
int tot, head[maxn];
void init()
{
    tot = 0;
    memset(head, -1, sizeof head);
}
void addedge(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
bool vis[maxn];
int sta[maxn], top;
bool dfs(int u)
{
    if(vis[u ^ 1])return false;
    if(vis[u])return true;
    vis[u] = true;
    sta[++top] = u;
    for(int i = head[u]; ~i; i = edge[i].next)
        if(!dfs(edge[i].to))
            return false;
    return true;
}
bool twosat(int n)
{
    memset(vis, false, sizeof vis);
    for(int i = 0; i < n; i += 2)
    {
        if(vis[i] || vis[i ^ 1])continue;
        top = -1;
        if(!dfs(i))
        {
            while(~top)vis[sta[top--]] = false;
            if(!dfs(i ^ 1))return false;
        }
    }
    return true;
}
vector<int> g[maxn];
int a[maxn];
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
    // ios_base::sync_with_stdio(0);
    // cin.tie(0);cout.tie(0);
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; ++i)scanf("%d", &a[i]);
    int x, k;
    bool flag = false;
    for(int i = 0; i < m; ++i)
    {
        scanf("%d", &k);
        for(int j = 1; j <= k; ++j)
        {
            scanf("%d", &x);--x;
            g[x].push_back(i);
        }
    }
    int len, u, v;
    init();
    for(int i = 0; i < n; ++i)
    {
        len = g[i].size();
        if(len == 1)
        {
            u = g[i][0];u <<= 1;
            if(a[i])
                addedge(u, u ^ 1);
            else
                addedge(u ^ 1, u);
        }
        else if(len == 2)
        {
            u = g[i][0], v = g[i][1];
            u <<= 1; v <<= 1;
            if(a[i])
            {
                addedge(u, v ^ 1);
                addedge(u ^ 1, v);
                addedge(v, u ^ 1);
                addedge(v ^ 1, u);
            }
            else
            {
                addedge(u, v);
                addedge(v, u);
                addedge(u ^ 1, v ^ 1);
                addedge(v ^ 1, u ^ 1);
            }
        }
        else if(len == 0)
        {
            if(a[i])
            {
                puts("NO");
                flag = true;
                break;
            }
        }
 
    }
    if(flag)return 0;
    if(twosat(m << 1))  puts("YES");
    else                puts("NO");
    return 0;  
}

tarjan

#include <bits/stdc++.h>
// #include <iostream>
// #include <cstdio>
// #include <cstdlib>
// #include <string.h>
// #include <vector>
// #include <cmath>
// #include <queue>
// #include <stack>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567 > 1e10
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e6 + 5;
const int maxm = 2e7 + 5;
const int mod = 1e9 + 7;

struct edge
{
    int to, next;
}edge[maxn];
int tot, head[maxn];
void init()
{
    int tot = 0;
    memset(head, -1, sizeof head);
}
void addedge(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
int low[maxn], dfn[maxn], sta[maxn], belong[maxn];
int idx, top;
int scc;
bool insta[maxn];
int num[maxn];
void tarjan(int u)
{
    int v;
    low[u] = dfn[u] = ++idx;
    sta[top++] = u;
    insta[u] = true;
    for(int i = head[u]; ~i; i = edge[i].next)
    {
        v = edge[i].to;
        if(!dfn[v])
        {
            tarjan(v);
            if(low[u] > low[v])low[u] = low[v];
        }
        else if(insta[v] && low[u] > dfn[v])
            low[u] = dfn[v];
    }
    if(low[u] == dfn[u])
    {
        ++scc;
        do
        {
            v = sta[--top];
            insta[v] = false;
            belong[v] = scc;
            ++num[scc];
        }while(v != u);
    }
}
bool twosat(int n)
{
    memset(dfn, 0, sizeof dfn);
    memset(insta, false, sizeof insta);
    memset(num, 0, sizeof num);
    idx = scc = top = 0;
    for(int i = 0; i < n; ++i)
        if(!dfn[i])
            tarjan(i);
    for(int i = 0; i < n; i += 2)
        if(belong[i] == belong[i ^ 1])
            return false;
    return true;
}
vector<int> g[maxn];
int a[maxn];
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
    // ios_base::sync_with_stdio(0);
    // cin.tie(0);cout.tie(0);
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i)scanf("%d", &a[i]);
    int x, k;
    bool flag = false;
    for(int i = 0; i <= n; ++i)g[i].clear();
    for(int i = 0; i < m; ++i)
    {
        scanf("%d", &k);
        for(int j = 1; j <= k; ++j)
        {
            scanf("%d", &x);
            g[x].push_back(i);
        }
    }
    int len, u, v;
    init();
    for(int i = 1; i <= n; ++i)
    {
        len = g[i].size();
        if(len == 1)
        {
            // u = g[i][0];u <<= 1;
            u = g[i][0] << 1;
            if(a[i])
                addedge(u, u ^ 1);
            else
                addedge(u ^ 1, u);
        }
        else if(len == 2)
        {
            // u = g[i][0]; v = g[i][1];
            // u <<= 1; v <<= 1;
            u = g[i][0] << 1;
            v = g[i][1] << 1;
            if(a[i])
            {
                addedge(u, v ^ 1);
                addedge(u ^ 1, v);
                addedge(v, u ^ 1);
                addedge(v ^ 1, u);
            }
            else
            {
                addedge(u, v);
                addedge(v, u);
                addedge(u ^ 1, v ^ 1);
                addedge(v ^ 1, u ^ 1);
            }
        }
        else if(len == 0)
        {
            if(a[i])
            {
                // puts("NO");
                // flag = true;
                // break;
                addedge(0, 1);
                addedge(1, 0);
            }
        }
    }
    if(twosat(m << 1))  puts("YES");
    else                puts("NO");
    return 0;   
}

其實這兩沒啥區別,,,總寫第一個是由於第一好寫(wu),,,tarjan稍慢一些(不過也沒多少),,,

寫了一天半的代碼,,wa了近20發,,,心態快崩了QAQ,,溜了溜了

D. The Door Problem

這道題和上面那道寶藏的題同樣,,(應該是牛客那道參考的這一道,,,

代碼都同樣直接交就好了,,建圖的思想相同,

Let's go home

按題意建圖就好了,,(剛開始忘記給點乘2,wa了幾發。。。

Astronauts UVALive - 3713

這題也不錯,,題面給了你三種選擇的限制關係,可是能夠用年齡化成兩種選擇的限制關係,,一開始寫出倆以後測試樣例的輸出和題所給的不同,一度懷疑是本身圖圖又建錯了,,後來想起來2sat問題不必定是惟一解啊,,把這個最重要的性質忘了,,我一直寫的那個解法是求字典序最小的解啊,,,QAQ。。

題意就是有這麼n個宇航員,每個人的年齡給你,而後有這麼3種任務,,最後一種是忽略年齡的,A任務要求只能年齡大於平均值的上,B任務則相反,,,其中一些人之間還有憎恨關係,即他倆不能在一個任務中,問你有沒有一個可行的安排任務的方式,,有的話輸出這種方式,,,

思路:我一開始想着要不要先無論AB任務的分配,也就是說將它倆當作一個任務,,而後和C任務選擇分配,,最後根據年齡來選擇AB任務的分配狀況,,,可是寫到一半發現不對,,對於年齡一個大於平均值的一個小於平均值的他們倆若是有憎恨關係的話他們倆是應該一個A一個B,,,若是按我那種想法的話就會出現兩個A的狀況(可能甚至不會出現,,由於這樣建圖會使得這種狀況選擇爲不取,,會出現兩個選C的狀況,,,

後來又想了一會,,想着反正對於一我的來講不是C就是AB中的一個,,那麼在建圖的時候判斷一下不就好了嗎,,,而後1A了,,,

// #include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
// #include <vector>
// #include <cmath>
// #include <queue>
// #include <stack>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;//1061109567 > 1e10
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e6 + 5;
const int maxm = 2e7 + 5;
const int mod = 1e9 + 7;

struct edge
{
    int to, next;
}edge[maxn];
int tot, head[maxn];
void init()
{
    tot = 0;
    memset(head, -1, sizeof head);
}
void addedge(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
bool vis[maxn];
int sta[maxn], top;
bool dfs(int u)
{
    if(vis[u ^ 1])return false;
    if(vis[u])return true;
    vis[u] = true;
    sta[++top] = u;
    for(int i = head[u]; ~i; i = edge[i].next)
        if(!dfs(edge[i].to))
            return false;
    return true;
}
bool twosat(int n)
{
    memset(vis, false, sizeof vis);
    for(int i = 0; i < n; i += 2)
    {
        if(vis[i] || vis[i ^ 1])continue;
        top = -1;
        if(!dfs(i))
        {
            while(~top)vis[sta[top--]] = false;
            if(!dfs(i ^ 1))return false;
        }
    }
    return true;
}
int a[maxn];
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
    // ios_base::sync_with_stdio(0);
    // cin.tie(0);cout.tie(0);
    int n, m;
    while(~scanf("%d%d", &n, &m) && n + m)
    {
        int sum = 0;
        for(int i = 1; i <= n; ++i)scanf("%d", &a[i]);
        for(int i = 1; i <= n; ++i)sum += a[i];
        init();
        int u, v;
        for(int i = 1; i <= m; ++i)
        {
            scanf("%d%d", &u, &v);
            if((a[u] * n <= sum && a[v] * n <= sum) || (a[u] * n >= sum && a[v] * n >= sum))
            {
                --u; --v;
                u <<= 1; v <<= 1;
                addedge(u, v ^ 1);
                addedge(u ^ 1, v);
                addedge(v, u ^ 1);
                addedge(v ^ 1, u);
            }
            else
            {
                --u; --v;
                u <<= 1; v <<= 1;
                // addedge(u, v);
                // addedge(v, u);
                // addedge(u, v ^ 1);
                // addedge(u ^ 1, v);
                // addedge(v, u ^ 1);
                // addedge(v ^ 1, u);
                addedge(u ^ 1, v);
                addedge(v ^ 1, u);
            }
            
        }
        if(!twosat(n << 1))puts("No solution.");
        else
        {
            for(int i = 0; i < n; ++i)
            {
                if(vis[i << 1])
                {
                    if(a[i + 1] * n < sum)puts("B");
                    else puts("A");
                }
                else puts("C");
            }
        }
        // for(int i = 0; i < 2 * n; i += 2)
        //     cout << vis[i] << vis[i ^ 1] << endl;
        
    }
    return 0;   
}

強連通份量的方法明天,啊不白天再說吧,,,溜了溜了

鴿了一個多月,,從寒假到三月底,,,emmmm
(loading)

2019-4-2 (end)

相關文章
相關標籤/搜索