「图论」2-SAT

【模板】2-SAT 问题

\(k-SAT\)问题\((k\geq 3)\)\(NP\)难题。

一个条件形如\(x_i\)\(a\)\(x_j\)\(b\)

如何表示一个状态必须选?

添加条件:\(x_i\)\(a\)\(x_i\)\(a\)

则表示,\(x_j\)\(\sim b\)时,\(x_i\)一定为\(a\)\(x_i\)\(\sim a\)时,\(x_j\)一定为\(b\)

连边就行,一条边表示如果有出发点状态\(s\),一定有目的点状态\(t\)

缩点,只要没有两个点在同一\(scc\)内则有解。

如何构造一个合法解?比较一个点两种状态的拓扑序,拓扑序较大的内个就是合法解。

为什么呢,拓扑序小的有可能走到拓扑序大的地方去,不能选,所以选大的。

\(tarjan\)缩点时,\(scc\)标号就是拓扑序的倒序。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=2e6+10,mod=998244353,inf=2e9;
    int n,m;
    vector<int> eg[N];
    int dfn[N],low[N],idx;
    int st[N],top;
    int col[N],num;
    inline void tarjan(int now)
    {
        dfn[now]=low[now]=++idx;
        st[++top]=now;
        for(int t:eg[now])
        {
            if(!dfn[t])
            {
                tarjan(t);
                low[now]=min(low[now],low[t]);
            }
            else if(!col[t]) low[now]=min(low[now],dfn[t]);
        }
        if(dfn[now]==low[now])
        {
            col[now]=++num;
            while(st[top]!=now) col[st[top--]]=num;
            --top;
        }
    }
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        cin>>n>>m;
        for(int i=1;i<=m;++i)
        {
            int x,b1,y,b2;
            cin>>x>>b1>>y>>b2;
            eg[x+(b1^1)*n].emplace_back(y+b2*n);
            eg[y+(b2^1)*n].emplace_back(x+b1*n);
        }
        for(int i=1;i<=n*2;++i)
        {
            if(!dfn[i]) tarjan(i);
        }
        for(int i=1;i<=n;++i)
        {
            if(col[i]==col[i+n])
            {
                cout<<"IMPOSSIBLE\n";
                return;
            }
        }
        cout<<"POSSIBLE\n";
        for(int i=1;i<=n;++i)
        {
            if(col[i]>col[i+n]) cout<<"1 ";
            else cout<<"0 ";
        }
    }
}
signed main()
{
    red::main();
    return 0;   
}
/*
1
1 2 3 4

*/

[POI2001] 和平委员会

[JSOI2010] 满汉全席

非常裸

[PA2010] Riddle

前缀和优化建边。

直接连边,连通块内部变\(n^2\)条边了,多多子。

前缀和优化,新增节点\(pre[i][0,1]\)表示之前有没有选择过节点表示状态\(1\)

那么有限制:

\[pre[i][0]->pre[i-1][0]\\ pre[i-1][1]->pre[i][1]\\ p[i][1]->pre[i][1]\\ pre[i][0]->p[i][0]\\ pre[i-1][1]->p[i][0]\\ p[i][1]->pre[i-1][0] \]

一定要狠狠地限制。

当然理论上为了至少选一个,还需要

\[pre[n][0]->pre[n][1] \]

那个题不知道为什么不加也过过了。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=4e6+10,mod=998244353,inf=2e9;
    int n,m,k;
    vector<int> eg[N];
    int a[N];
    int dfn[N],low[N],idx;
    int st[N],top;
    int col[N],num;
    inline int p0(int x){return x+n;}
    inline int p1(int x){return x;}
    inline int pre1(int x){return x+2*n;}
    inline int pre0(int x){return x+3*n;}
    inline void tarjan(int now)
    {
        dfn[now]=low[now]=++idx;
        st[++top]=now;
        for(int t:eg[now])
        {
            if(!dfn[t])
            {
                tarjan(t);
                low[now]=min(low[now],low[t]);
            }
            else if(!col[t]) low[now]=min(low[now],dfn[t]);
        }
        if(dfn[now]==low[now])
        {
            col[now]=++num;
            while(st[top]!=now) col[st[top--]]=num;
            --top;
        }
    }
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        int T;T=1;
        while(T--)
        {
            cin>>n>>m>>k;
            for(int i=1;i<=m;++i)
            {
                int x,y;
                cin>>x>>y;
                eg[p0(x)].emplace_back(p1(y));
                eg[p0(y)].emplace_back(p1(x));
            }   
            for(int i=1;i<=k;++i)
            {
                int w;cin>>w;
                for(int j=1;j<=w;++j)
                {
                    cin>>a[j];
                    eg[p1(a[j])].emplace_back(pre1(a[j]));
                    eg[pre0(a[j])].emplace_back(p0(a[j]));
                }
                for(int j=2;j<=w;++j)
                {
                    eg[pre1(a[j-1])].emplace_back(pre1(a[j]));
                    eg[pre0(a[j])].emplace_back(pre0(a[j-1]));
                    eg[pre1(a[j-1])].emplace_back(p0(a[j]));
                    eg[p1(a[j])].emplace_back(pre0(a[j-1]));
                    if(j==w)
                    {
                        eg[pre0(a[w])].emplace_back(pre1(a[w]));
                    }
                }
            }
            for(int i=1;i<=4*n;++i)
            {
                if(!dfn[i]) tarjan(i);
            }
            bool flag=0;
            for(int i=1;i<=n;++i)
            {
                if(col[p0(i)]==col[p1(i)]||col[pre0(i)]==col[pre1(i)])
                {
                    cout<<"NIE\n";flag=1;
                    break;
                }
            }
            if(!flag) cout<<"TAK\n";
        }
        
    }
}
signed main()
{
    red::main();
    return 0;   
}
/*
1
1 2 3 4

*/

[POI2011]KON-Conspiracy

先构造个合法解出来捏。

然后我们发现,某一个阵容的两个人不可能同时换去对面阵容。

我们对每个人根据当前的阵容,设置一个值\(c_i\),表示在对面阵容中和自己冲突的人数。

如果\(c_i==0\),太好了,随便跳槽。

如果\(c_i==1\),如果和自己冲突的那个人的\(c\)是零,它们俩可以换换。

\(c_i==0\)的内类人要乘起来。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=10000+10,mod=998244353,inf=2e9;
    int n,m,k,ans;
    vector<signed> eg[N];
    signed dfn[N],low[N],idx;
    signed st[N],top;
    signed col[N],num;
    signed b[N];
    signed ct[N];
    bool vis[5010][5010];
    inline int p0(int x){return x+n;}
    inline int p1(int x){return x;}
    inline void tarjan(int now)
    {
        dfn[now]=low[now]=++idx;
        st[++top]=now;
        for(int t:eg[now])
        {
            if(!dfn[t])
            {
                tarjan(t);
                low[now]=min(low[now],low[t]);
            }
            else if(!col[t]) low[now]=min(low[now],dfn[t]);
        }
        if(dfn[now]==low[now])
        {
            col[now]=++num;
            while(st[top]!=now) col[st[top--]]=num;
            --top;
        }
    }
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;++i)
        {
            int w;cin>>w;
            for(int j=1;j<=w;++j)
            {
                int x;cin>>x;
                vis[i][x]=1;
                eg[p1(i)].emplace_back(p0(x));
            }
            for(int j=1;j<=n;++j) if(!vis[i][j]&&j!=i)
            {
                eg[p0(i)].emplace_back(p1(j));
            }
        }
        for(int i=1;i<=2*n;++i)
        {
            if(!dfn[i]) tarjan(i);
        }
        int s1=0,s2=0;
        for(int i=1;i<=n;++i)
        {
            if(col[p0(i)]==col[p1(i)])
            {
                cout<<0<<'\n';
                return;
            }
            if(col[p0(i)]<col[p1(i)]) ++s1;
            else b[i]=1,++s2;
        }
        if(s1>0&&s2>0) ++ans;
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=n;++j) if(i!=j)
            {
                if(b[i]==1&&b[j]==0&&vis[i][j]==0) ++ct[i];
                if(b[i]==0&&b[j]==1&&vis[i][j]==1) ++ct[i];
            }
        }
        int t1=0,t2=0;
        for(int i=1;i<=n;++i)
        {
            if(ct[i]==0)
            {
                if(b[i]==0)
                {
                    ++t1;
                    if(s1>1) ++ans;
                }
                else
                {
                    ++t2;
                    if(s2>1) ++ans;
                }
            }
            for(int j=1;j<=n;++j) if(i!=j)
            {
                if(ct[i]==1&&ct[j]==0&&b[i]!=b[j]) ++ans;
            }
        }
        ans+=t1*t2;
        cout<<ans<<'\n';
    }
}
signed main()
{
    red::main();
    return 0;   
}
/*
1
1 2 3 4

*/

[北京省选集训2019]完美塔防

真是好题啊

先预处理出每个炮台横着,竖着会不会打到其他炮台。

然后考虑空地,一个空地如果会被炮台横着穿过,那么只有一个炮台满足要求。因为光路可逆,如果有两个炮台能横着打到同一块空地,他俩就互相biubiu了。

竖着同理,所以一个空地最多被两个炮台穿过。

然后就是显然的\(2-SAT\)了。

再加上亿点点调试,就过了。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=10000+10,mod=998244353,inf=2e9;
    int n,m;
    bool flag;
    char s[55][55];
    vector<int> eg[N];
    typedef pair<int,int> pr;
    vector<int> sw[55][55];
    bool vis[55][55][4];
    pr q[N];int tot;
    int dfn[N],low[N],st[N],col[N],idx,num,top;
    inline void tarjan(int now)
    {
        dfn[now]=low[now]=++idx;
        st[++top]=now;
        for(int t:eg[now])
        {
            if(!dfn[t])
            {
                tarjan(t);
                low[now]=min(low[now],low[t]);
            }
            else if(!col[t]) low[now]=min(low[now],dfn[t]);
        }
        if(dfn[now]==low[now])
        {
            col[now]=++num;
            while(st[top]!=now) col[st[top--]]=num;
            --top;
        }
    }
    inline int id(int x,int y,int z)
    {
        return (x-1)*m+y+z*n*m;
    }
    inline int bit(int x)
    {
        if(x>n*m) return x-n*m;
        return x+n*m;
    }
    inline bool dfs(int x,int y,int t,int st)
    {
        //cout<<x<<' '<<y<<' '<<t<<"!!"<<endl;
        if(x<1||x>n||y<1||y>m) return 1;
        if(s[x][y]=='#') return 1;
        if(vis[x][y][t]) return 1;
        if((s[x][y]=='-'||s[x][y]=='|'))
        {
            return 0;
        }
        //if(st==id(4,3,0)) cout<<x<<' '<<y<<"!!!!!!!!!!!!!!"<<endl;
        vis[x][y][t]=1;
        if(s[x][y]=='/')
        {
            if(t==0) return dfs(x,y+1,1,st);
            if(t==1) return dfs(x-1,y,0,st);
            if(t==2) return dfs(x,y-1,3,st);
            if(t==3) return dfs(x+1,y,2,st);
        }
        if(s[x][y]=='\\')
        {
            if(t==0) return dfs(x,y-1,3,st);
            if(t==1) return dfs(x+1,y,2,st);
            if(t==2) return dfs(x,y+1,1,st);
            if(t==3) return dfs(x-1,y,0,st);
        }
        bool tmp=0;
        if(t==0) tmp=dfs(x-1,y,0,st);
        if(t==1) tmp=dfs(x,y+1,1,st);
        if(t==2) tmp=dfs(x+1,y,2,st);
        if(t==3) tmp=dfs(x,y-1,3,st);
        if(!tmp) return 0;
        //cout<<x<<' '<<y<<' '<<t<<' '<<st<<"!!"<<endl;
        if(count(sw[x][y].begin(),sw[x][y].end(),st)==0) sw[x][y].emplace_back(st);
        return 1;
    }
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        int T;cin>>T;
        while(T--)
        {
            cin>>n>>m;
            idx=top=num=tot=0;flag=0;
            for(int i=1;i<=n*m*2;++i)
            {
                dfn[i]=low[i]=col[i]=0;
                eg[i].clear();
            }
            for(int i=1;i<=n;++i)
            {
                cin>>(s[i]+1);
                for(int j=1;j<=m;++j)
                {
                    sw[i][j].clear();
                }
            }
            for(int i=1;i<=n;++i)
            {
                for(int j=1;j<=m;++j)
                {
                    if(s[i][j]!='-'&&s[i][j]!='|') continue;
                    for(int k=1;k<=n;++k) for(int p=1;p<=m;++p) for(int o=0;o<=3;++o) vis[k][p][o]=0;
                    int t0=dfs(i,j+1,1,id(i,j,0))&dfs(i,j-1,3,id(i,j,0));
                    for(int k=1;k<=n;++k) for(int p=1;p<=m;++p) for(int o=0;o<=3;++o) vis[k][p][o]=0;
                    int t1=dfs(i-1,j,0,id(i,j,1))&dfs(i+1,j,2,id(i,j,1));
                    //cout<<i<<' '<<j<<' '<<t0<<' '<<t1<<"!!"<<endl;
                    if(t0==0&&t1==0) flag=1;
                    if(t0==0) q[++tot]=pr(id(i,j,1),id(i,j,1));
                    if(t1==0) q[++tot]=pr(id(i,j,0),id(i,j,0));
                }
            }
            for(int i=1;i<=n;++i)
            {
                for(int j=1;j<=m;++j)
                {
                    if(s[i][j]!='.') continue;
                    if(sw[i][j].size()==0) flag=1;
                    int t0=0,t1=0;
                    if(sw[i][j].size()>=1) t0=sw[i][j][0];
                    if(sw[i][j].size()>=2) t1=sw[i][j][1];
                    if(!t1) q[++tot]=pr(t0,t0);
                    else q[++tot]=pr(t0,t1);
                }
            }
            for(int i=1;i<=tot;++i)
            {
                //cout<<q[i].first<<' '<<q[i].second<<"!!"<<endl;
                eg[bit(q[i].first)].emplace_back(q[i].second);
                eg[bit(q[i].second)].emplace_back(q[i].first);
            }
            for(int i=1;i<=n*m*2;++i)
            {
                if(!dfn[i]) tarjan(i);
            }
            for(int i=1;i<=n*m;++i)
            {
                if(col[i]==col[i+n*m]) flag=1;
            }
            if(flag)
            {
                cout<<"IMPOSSIBLE\n";
                continue;
            }
            cout<<"POSSIBLE\n";
            for(int i=1;i<=n;++i)
            {
                for(int j=1;j<=m;++j)
                {
                    if(s[i][j]=='-'||s[i][j]=='|')
                    {
                        if(col[id(i,j,0)]<col[id(i,j,1)]) cout<<'-';
                        else cout<<'|';
                    }
                    else cout<<s[i][j];
                }
                cout<<'\n';
            }
        }
    }
}
signed main()
{
    red::main();
    return 0;   
}
/*
1
4 4
#\.-
\..-
-\.-
####


1
3 4
#.##
#--#
####

*/

[NOI2017] 游戏

同样是妙妙题。

考虑只有\(8\)个位置是\(x\),可以优先枚举\(x\)是什么。

需要\(3\)进制枚举吗?不需要!只要枚举\(x\)\(a\)还是\(c\)就行了,因为这样不管它是\(A,B\)还是\(C\)都能取到。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
    const int N=2e5+10,mod=998244353,inf=2e9;
    int n,m,k;
    char s[N],t[N];
    int posx[N];
    vector<int> eg[N];
    int dfn[N],low[N],col[N],st[N];
    int idx,num,top;
    struct node
    {
        int x,y;
        char hx,hy;
    }q[N];
    inline int id(int x,int y)
    {
        return x+y*n;
    }
    inline void tarjan(int now)
    {
        dfn[now]=low[now]=++idx;
        st[++top]=now;
        for(int t:eg[now])
        {
            if(!dfn[t])
            {
                tarjan(t);
                low[now]=min(low[now],low[t]);
            }
            else if(!col[t]) low[now]=min(low[now],dfn[t]);
        }
        if(dfn[now]==low[now])
        {
            col[now]=++num;
            while(st[top]!=now) col[st[top--]]=num;
            --top;
        }
    }
    inline bool check()
    {
        idx=top=num=0;
        for(int i=1;i<=n*2;++i)
        {
            dfn[i]=low[i]=col[i]=0;
            eg[i].clear();
        }
        for(int i=1;i<=k;++i)
        {
            int t1,t2;
            if(t[q[i].x]=='a'||t[q[i].x]=='b') t1=(q[i].hx=='C');
            else t1=(q[i].hx=='B');
            if(t[q[i].y]=='a'||t[q[i].y]=='b') t2=(q[i].hy=='C');
            else t2=(q[i].hy=='B');
            if(q[i].hx-'A'+'a'==t[q[i].x]) continue;
            if(q[i].hy-'A'+'a'==t[q[i].y])
            {
                eg[id(q[i].x,t1)].emplace_back(id(q[i].x,t1^1));
                continue;
            }
            eg[id(q[i].x,t1)].emplace_back(id(q[i].y,t2));
            eg[id(q[i].y,t2^1)].emplace_back(id(q[i].x,t1^1));
        }
        for(int i=1;i<=2*n;++i)
        {
            if(!dfn[i]) tarjan(i);
        }
        for(int i=1;i<=n;++i)
        {
            if(col[i]==col[i+n]) return 0;
        }
        for(int i=1;i<=n;++i)
        {
            char s1,s2;
            if(t[i]=='a') s1='B',s2='C';
            if(t[i]=='b') s1='A',s2='C';
            if(t[i]=='c') s1='A',s2='B';
            if(col[i]<col[i+n]) cout<<s1;
            else cout<<s2;
        }
        return 1;
    }
    inline void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        // 二进制枚举x 是等于c还是等于C
        cin>>n>>m;
        cin>>(s+1);
        for(int i=1,p=0;i<=n;++i)
        {
            if(s[i]=='x') posx[p++]=i;
        }
        cin>>k;
        for(int i=1;i<=k;++i)
        {
            cin>>q[i].x>>q[i].hx>>q[i].y>>q[i].hy;
        }
        int S=(1<<m);
        for(int ss=0;ss<S;++ss)
        {
            for(int i=1;i<=n;++i) t[i]=s[i];
            for(int k=0;k<m;++k)
            {
                if((ss>>k)&1) t[posx[k]]='c';
                else t[posx[k]]='a';
            }
            if(check()) return;
        }
        cout<<"-1\n";
    }
}
signed main()
{
    red::main();
    return 0;   
}
/*
5 0
bcbbb
10
1 B 1 C
3 C 1 C
2 B 5 A
1 C 1 C
2 A 5 A
3 A 1 C
2 B 1 C
3 A 1 B
1 B 4 B
1 B 4 A


*/
posted @ 2022-04-18 16:49  lovelyred  阅读(56)  评论(0)    收藏  举报