Graph theory 0x04 【SAT】

先续0x03的Tarjan

HDU 3836 Equivalent Sets 给有向图,至少添加多少边才成为强连通图?
思路:tarjan缩点。
对于强连通缩点图中的每一个点,一定至少有一个出度和一个入度。
我们就求缩点图中有多少个点的入度为0,多少个点的出度为0.
答案就是max(a1,a2)。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 20004;
int low[maxn],num[maxn],dfn,cnt,sccno[maxn],in[maxn],out[maxn];
stack<int>s;
vector<int>G[maxn];

void dfs(int u){
    s.push(u);
    num[u]=low[u]=++dfn;
    for(int i=0;i<G[u].size();++i){
        int v=G[u][i];
        if(!num[v]){
            dfs(v);
            low[u]=min(low[v],low[u]);
        }else if(!sccno[v])
            low[u]=min(low[u],num[v]);
    }
    if(low[u]==num[u]){
        cnt++;
        while(1){
            int v=s.top();s.pop();
            sccno[v]=cnt;
            if(u==v) break;
        }
    }
}

void Tarjan(int n){
    memset(num,0,sizeof(num));
    memset(sccno,0,sizeof(sccno));
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    dfn=cnt=0;
    for(int i=1;i<=n;++i)
        if(!num[i]) dfs(i);
    for(int i=1;i<=n;++i){
        for(int j=0;j<G[i].size();++j){
            int v=G[i][j];
            if(sccno[i]==sccno[v]) continue;
            in[sccno[v]]++;out[sccno[i]]++;
        }
    }
    int a1=0,a2=0;
    for(int i=1;i<=cnt;++i) {
        if(in[i]==0) a1++;
        if(out[i]==0) a2++;
    }
    if(cnt==1) cout<<0<<endl;
    else cout<<max(a1,a2)<<endl;
}
// max(入度=0的缩点个数,出度=0的缩点个数)

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        for(int i=1;i<=n;++i) G[i].clear();
        for(int i=1;i<=m;++i){
            int u,v;scanf("%d%d",&u,&v);
            G[u].push_back(v);
        }
        Tarjan(n);
    }
}

HDU 3639 Hawk-and-Chicken 投票具有传递性 求最大得票
题意:一个投票,规则:投票具有传递性,A支持B,B支持C,那么C获得2票(A.B共两票),输出最多能获得的票数是多少张和获得最多票数的人是谁?(如果有多个人获得的票数都是最多的,就将他们全部输出)。
思路:tarjan缩点。缩点图每个点内部得票数一样。缩点图只有出度为0的点有可能得票最多。统计出度为0的点的得票数。如何统计?建反图,出度为0的点的得票即反图入度为0的点进行MST的size。

#include<bits/stdc++.h>
using namespace std;
/*
 * 建反图。求sz。
 */

const int maxn = 5003;
int low[maxn],num[maxn],dfn,cnt,mx,sccno[maxn],in[maxn],id,sccn[maxn];
vector<int>G[maxn];
vector<int>G2[maxn];  // 反图
stack<int>s;

void dfs(int u){
    s.push(u);
    num[u]=low[u]=++dfn;
    for(int i=0;i<G[u].size();++i){
        int v=G[u][i];
        if(!num[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);
        }else if(!sccno[v])
            low[u]=min(low[u],num[v]);
    }
    if(num[u]==low[u]){
        cnt++;
        while(1){
            int v=s.top();s.pop();
            sccno[v]=cnt;
            sccn[cnt]++;
            if(u==v) break;
        }
    }
}
int sup[maxn],vis[maxn],sm;
void dfs2(int u){
    vis[u]=1;
    sm+=sccn[u];
    for(int i=0;i<G2[u].size();++i){
        int v=G2[u][i];
        if(!vis[v]) dfs2(v);
    }
}

void init(int n){
    memset(num,0,sizeof(num));
    memset(sccno,0,sizeof(sccno));
    memset(in,0,sizeof(in));
    memset(sccn,0,sizeof(sccn));
    memset(sup,0,sizeof(sup));
    for(int i=1;i<=n;++i) G2[i].clear();
    for(int i=1;i<=n;++i) G[i].clear();
    dfn=cnt=mx=0;
}

void Tarjan(int n){
    for(int i=1;i<=n;++i) if(!num[i]) dfs(i);
    for(int i=1;i<=n;++i){
        for(int j=0;j<G[i].size();++j){
            int v=G[i][j];
            if(sccno[i]==sccno[v]) continue;
            G2[sccno[v]].push_back(sccno[i]);
            in[sccno[i]]++;
        }
    }
    for(int i=1;i<=cnt;++i) if(!in[i]) {
        sm=0;
        memset(vis,0,sizeof(vis));
        dfs2(i);
        sup[i]=sm-1;
        mx=max(mx,sup[i]);
    }

    cout<<"Case "<<++id<<": "<<mx<<endl;
    int isf=1;
    for(int i=1;i<=n;++i)
        if(sup[sccno[i]]==mx) {
            if(isf) cout<<i-1,isf=0;
            else cout<<" "<<i-1;
        }
    putchar(10);
}

int main(){
    int t;scanf("%d",&t);
    while(t--){
        int n,m;scanf("%d%d",&n,&m);
        init(n);
        for(int i=1;i<=m;++i){
            int u,v;scanf("%d%d",&u,&v);
            G[++u].push_back(++v);
        }
        Tarjan(n);
    }
}

HDU 1530 Maximum Clique 最大团简单题 dfs爆搜

#include<bits/stdc++.h>
using namespace std;
const int maxn = 53;
int mp[maxn][maxn];
int mx,n;

int point[maxn];

bool isclique(int num,int p){
    for(int i=1;i<=num;++i)
        if(!mp[point[i]][p])
            return 0;
    return 1;
}

void dfs(int num,int p){
    for(int i=p;i<=n;++i){
        if(isclique(num,i)){
            point[num+1]=i;
            dfs(num+1,i+1);
        }
    }
    mx=max(mx,num);
}

int main(){
    while(~scanf("%d",&n),n){
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                scanf("%d",&mp[i][j]);
        mx=0;
        dfs(0,1);
        cout<<mx<<endl;
    }
}

2-SAT

非常详细的讲解博客!
HDU 3062 Party 2sat入门题
给出不能同时出现的关系。
判断是否可行。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1003;
vector<int>G[maxn<<1];  // 原图
stack<int>s;
int low[maxn<<1],num[maxn<<1],sccno[maxn<<1],dfn,cnt;

void dfs(int u){
    low[u]=num[u]=++dfn;
    s.push(u);
    for(int i=0;i<G[u].size();++i){
        int v=G[u][i];
        if(!num[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);
        }else if(!sccno[v])
            low[u]=min(low[u],num[v]);
    }
    if(low[u]==num[u]){
        cnt++;
        while(1){
            int v=s.top();s.pop();
            sccno[v]=cnt;
            if(u==v) break;
        }
    }
}

bool Tarjan(int n){
    for(int i=0;i<n;++i)
        if(!num[i]) dfs(i);
    for(int i=0;i<n-1;i+=2)
        if(sccno[i]==sccno[i+1]) return 0;
    return 1;
}

void init(int n){
    for(int i=0;i<n;++i) G[i].clear();
    memset(num,0,sizeof(num));
    memset(sccno,0,sizeof(sccno));
    dfn=cnt=0;
}

int main(){
    int n,m;while(~scanf("%d%d",&n,&m)){
        init(2*n);
        for(int i=1;i<=m;++i){
            int f1,f2,u,v;scanf("%d%d%d%d",&u,&v,&f1,&f2);
            G[u<<1|f1].push_back(v<<1|(f2^1));
            G[v<<1|f2].push_back(u<<1|(f1^1));
        }
        if(Tarjan(2*n)) puts("YES");
        else puts("NO");
    }
}

HDU 1824 Let’s go home sat简单题
这种题居然一发过了。。好神奇

  • 考虑一对1与0:即队长留下是1,队友留下是0.
  • 建图。
  • 用一个map储存编号的对应关系。
  • 非此即彼:A->B’, B->A’.
  • 当非此即彼的是同一个队伍的队友,必选队长。队友到队长连一条边。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3004;
int b[maxn];
vector<int>G[maxn];
stack<int>s;
int low[maxn],num[maxn],cnt,sccno[maxn],dfn;

void dfs(int u){
    num[u]=low[u]=++dfn;
    s.push(u);
    for(int i=0;i<G[u].size();++i){
        int v=G[u][i];
        if(!num[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);
        }else if(!sccno[v])
            low[u]=min(low[u],num[v]);
    }
    if(low[u]==num[u]){
        cnt++;
        while(1){
            int v=s.top();s.pop();
            sccno[v]=cnt;
            if(u==v) break;
        }
    }
}

void Tarjan(int n){
    for(int i=0;i<n;++i) if(!num[i]) dfs(i);
}

void init(int n){
    for(int i=0;i<n;++i) G[i].clear();
    memset(num,0,sizeof(num));
    memset(sccno,0,sizeof(sccno));
    dfn=cnt=0;
}

int main(){
    int t,m;
    while(~scanf("%d%d",&t,&m)){
        int cnt2=0;
        init(2*t);
        for(int i=1;i<=t;++i){
            int u,v,x;scanf("%d%d%d",&u,&v,&x);
            b[u]=cnt2++;  // 偶数点:队长
            b[v]=cnt2,b[x]=cnt2++; // 单数点:队员
        }
        int flg=0;
        for(int i=1;i<=m;++i){
            int u,v,ru,rv;scanf("%d%d",&u,&v);
            u=b[u],v=b[v],ru=(u&1)?u-1:u+1,rv=(v&1)?v-1:v+1;
            if(u==v){G[u].push_back(u-1);continue;}
            G[u].push_back(rv);
            G[v].push_back(ru);
        }
        Tarjan(2*t);
        for(int i=0;i<2*t;i+=2){
            if(sccno[i]==sccno[i+1]) {flg=1;break;}
        }
        if(flg) cout<<"no"<<endl;
        else cout<<"yes"<<endl;
    }
}

HDU 4115 Eliminate the Conflict 2-SAT
这样考虑:
SAT问题都是解决1/0的问题的。
正好,每轮Alice也只能在两个选项中选一个。那么其一算0,其二算1.
两轮不能选一样的:那么当一样的时候,选另一个。
两轮一定要选一样的:当不一样的时候,选另一个。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 20003;
int a[maxn];
vector<int>G[maxn];
stack<int>s;
int num[maxn],low[maxn],dfn,cnt,sccno[maxn];
int b[maxn];  // 用来储存每个点分别是哪一号人物

void dfs(int u){
    num[u]=low[u]=++dfn;
    s.push(u);
    for(int i=0;i<G[u].size();++i){
        int v=G[u][i];
        if(!num[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);
        }else if(!sccno[v])
            low[u]=min(low[u],num[v]);
    }
    if(num[u]==low[u]){
        cnt++;
        while(1){
            int v=s.top();s.pop();
            sccno[v]=cnt;
            if(u==v) break;
        }
    }
}

void Tarjan(int n){
    for(int i=0;i<n;++i) if(!num[i]) dfs(i);
}

void init(int n){
    for(int i=0;i<=n;++i) G[i].clear();
    memset(num,0,sizeof(num));
    memset(sccno,0,sizeof(sccno));
    dfn=cnt=0;
}

int main(){
    int t,cas=0;cin>>t;
    while(t--){
        int n,m;cin>>n>>m;
        init(n<<1);
        for(int i=0;i<n;++i){
            scanf("%d",&a[i]);
            if(a[i]==1) b[2*i]=2,b[2*i+1]=3;
            if(a[i]==2) b[2*i]=1,b[2*i+1]=3;
            if(a[i]==3) b[2*i]=1,b[2*i+1]=2;
        }
        for(int i=1;i<=m;++i){
            int u,v,opt;scanf("%d%d%d",&u,&v,&opt);
            u--,v--;
            if(opt){
                // 要两者不同。只需要让两者相同的那两个转向对方另一个。
                for(int j=2*u;j<=2*u+1;++j){
                    for(int k=2*v;k<=2*v+1;++k){
                        if(b[j]==b[k]){
                            G[j].push_back(k^1);
                            G[k].push_back(j^1);
                        }
                    }
                }
            }else{
            // 要两者相同,那么不同的时候转向另一个
                for(int j=2*u;j<=2*u+1;++j){
                    for(int k=2*v;k<=2*v+1;++k){
                        if(b[j]!=b[k]){
                            G[j].push_back(k^1);
                            G[k].push_back(j^1);
                        }
                    }
                }
            }
        }
        Tarjan(n<<1);
        int flg=0;
        for(int i=0;i<(n<<1);i+=2){
            if(sccno[i]==sccno[i+1]) {flg=1;break;}
        }
        cout<<"Case #"<<++cas<<": ";
        if(flg) cout<<"no"<<endl;
        else cout<<"yes"<<endl;
    }
}

HDU 4421 Bit Magic
题意:

a[n]是由b[n][n]经过以上函数得到,现给出b,问是否有可行a。
一开始的做法把每个Bit都建了一个点,然后MLE了(。
现在改为对每个bit都进行一次2-sat。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 503*2;
vector<int>G[maxn];
int num[maxn],low[maxn],sccno[maxn],dfn,cnt;
stack<int>s;
void init(int n){
    for(int i=0;i<n;++i) G[i].clear();
    memset(num,0,sizeof(num));
    memset(sccno,0,sizeof(sccno));
    dfn=cnt=0;
}

void dfs(int u){
    num[u]=low[u]=++dfn;
    s.push(u);
    for(int i=0;i<G[u].size();++i){
        int v=G[u][i];
        if(!num[v]){
            dfs(v);
            low[u]=min(low[v],low[u]);
        }else if(!sccno[v])
            low[u]=min(low[u],num[v]);
    }
    if(num[u]==low[u]){
        cnt++;
        while(1){
            int v=s.top();s.pop();
            sccno[v]=cnt;
            if(u==v) break;
        }
    }
}

void Tarjan(int n){
    for(int i=0;i<n;++i) if(!num[i]) dfs(i);
}

int b[maxn][maxn];
int main(){
    int n;
    while(~scanf("%d",&n)){
        for(int i=0;i<n;++i) for(int j=0;j<n;++j)
            scanf("%d",&b[i][j]);
        int flg=1;
        for(int i=0;i<n&&flg;++i){
            if(b[i][i]!=0) flg=0;
            for(int j=i+1;j<n&&flg;++j)
                if(b[i][j]!=b[j][i]) flg=0;
        }
        if(!flg){cout<<"NO"<<endl;continue;}
        for(int k=0;k<31&&flg;++k){
            init(n<<1);
            for(int i=0;i<n;++i){
                for(int j=i+1;j<n;++j){
                    int is1=b[i][j]&(1<<k);
                    int u=2*i,v=2*j;
                    if((i%2)&&(j%2)){
                        if(is1){
                            G[u].push_back(v^1);
                            G[v].push_back(u^1);
                        }else{
                            G[u^1].push_back(u);
                            G[v^1].push_back(v);
                        }
                    }else if(!(i%2)&&!(j%2)){
                        if(is1){
                            G[u].push_back(u^1);
                            G[v].push_back(v^1);
                        }else{
                            G[u^1].push_back(v);
                            G[v^1].push_back(u);
                        }
                    }else{
                        if(is1){
                            G[u].push_back(v^1);
                            G[v].push_back(u^1);
                            G[u^1].push_back(v);
                            G[v^1].push_back(u);
                        }else{
                            G[u^1].push_back(v^1);
                            G[v^1].push_back(u^1);
                            G[u].push_back(v);
                            G[v].push_back(u);
                        }
                    }
                }
            }
            Tarjan(n<<1);
            for(int i=0;i<(n<<1);i+=2){
                if(sccno[i]==sccno[i+1]) {flg=0;break;}
            }
        }
        if(!flg) cout<<"NO"<<endl;
        else cout<<"YES"<<endl;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值