UOJ210【UER #6】寻找罪犯 (2-SAT前后缀优化建边)

本文介绍了一道名为UOJ210的算法题目——寻找罪犯,该题涉及n个嫌疑人及m条供词,并探讨了如何通过拓扑排序和强连通分量(SCC)来确定犯人身份的有效方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UOJ210【UER #6】寻找罪犯

原题地址http://uoj.ac/problem/210

题意:
n 个嫌疑人,编号为 1 到 n,严刑拷打之下,他们交代了一些供词,供词有两类:
1.xi 说 yi 是犯人。
2.xi 说 yi 不是犯人。
犯人们的供词不总是真的,每一个犯人的所有供词最多有一句是假的,而不是犯人的嫌疑人的供词总是真的。
现在给出了全部的 m 条供词,你需要找出哪些人是犯人。如果有多解,输出任何一组解即可

数据范围
n,m<=1e5

题解:
贴好久以前的代码…
当时比较naive,前缀、后缀、供词和人的点都建了,实际上只需要建前缀和人即可。

利用scc判断的方式还未懂,为什么tarjan求到的scc顺序是top序或其逆序的?

这里有一个初步的想法是考虑tarjan的过程,它是弹栈然后求得一些scc,那么先求得的scc的拓扑序靠后,
于是可以直接选择scc编号小(topu靠后)的那些点。
待UPD。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
using namespace std;
const int N=1000000;
const int M=100005;
int idc,num,inc,top,cnt;
vector<int> pre[M],fake[M],suf[M];         //2*n真 2*n^1假 
vector< pair<int,int> > s[M],e;
queue<int> q;
int head[N],to[10*N],nxt[10*N];
bool vis[N];
int dfn[N],low[N],stack[N],p[N],in[N],col[N],conf[N ];
int n,m;
void build(int u,int v)
{
    e.push_back(make_pair(u,v));
    num++;
    to[num]=v;
    nxt[num]=head[u];
    head[u]=num;
}
void add(int u,int v)
{
    build(u,v); build(v^1,u^1);
}
void dfs(int u)
{
    inc++; dfn[u]=low[u]=inc;
    top++; stack[top]=u; vis[u]=1;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(!dfn[v]) 
        {
            dfs(v);
            if(low[v]<low[u]) low[u]=low[v];
        }
        if(vis[v]&&dfn[v]<low[u]) low[u]=dfn[v];
    }
    if(low[u]==dfn[u])
    {
        cnt++;
        while(1)
        {
            p[stack[top]]=cnt;
            vis[stack[top]]=0;
            top--;
            if(stack[top+1]==u) break;
        }
    }
}
void init()
{
    memset(head,0,sizeof(head));
    memset(in,0,sizeof(in));
    num=0;
    int sz=e.size();
    for(int i=0;i<sz;i++)
    {
        int u=e[i].first; int v=e[i].second;
        if(p[u]==p[v]) continue;
        build(p[u],p[v]);
        in[p[v]]++;
    }
}
void tp()
{
    memset(col,0,sizeof(col));

    for(int i=1;i<=cnt;i++)
    {
        if(in[i]==0) 
        {
            q.push(i);  
            col[i]=1; 
            col[conf[i]]=2; 
        }   
    }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=nxt[i])
        {
            int v=to[i];
            if(!col[v]) {col[v]=1; col[conf[v]]=2;}
            in[v]--;
            if(in[v]==0) q.push(v);

        }
    }
}
int main()
{
    memset(vis,0,sizeof(vis));
    memset(dfn,0,sizeof(dfn));
    scanf("%d%d",&n,&m);
    idc=2*n+1; num=0;
    while(m--)
    {
        int x,y,t;
        scanf("%d%d%d",&x,&y,&t);
        s[x].push_back(make_pair(y,t^1));  //奇数真,偶数假 ,记录的是奇数编号 
        idc+=2; pre[x].push_back(idc);
        idc+=2; fake[x].push_back(idc);
        idc+=2; suf[x].push_back(idc);      
    }
    for(int u=1;u<=n;u++)
    {
        int sz=s[u].size();
        if(sz) {add(u<<1,pre[u][sz-1]);add(u<<1,suf[u][0]); } 
        for(int i=0;i<sz;i++)
        {
            int v=s[u][i].first;
            if(s[u][i].second)  add(fake[u][i],(v<<1)^1);  //若此条供词为真,向对应的人建边 
            else add(fake[u][i],v<<1);
            if(i!=0) {  add(fake[u][i]^1,pre[u][i-1]); add(pre[u][i],pre[u][i-1]);}
            if(i!=sz-1) {add(fake[u][i]^1,suf[u][i+1]); add(suf[u][i],suf[u][i+1]); }
            add(pre[u][i],fake[u][i]); add(suf[u][i],fake[u][i]);       
        }
    }
    idc++;inc=0;top=0; cnt=0;
    for(int i=2;i<=idc;i++)
    {
        if(!dfn[i]) dfs(i);
    }
    for(int i=2;i<=idc;i+=2)
    {
        if(p[i]==p[i^1]) {
            printf("Impossible"); return 0;
        }
        if(!conf[p[i]])
        {
            conf[p[i]]=p[i^1];
            conf[p[i^1]]=p[i];
        }       
    }
    init();
    tp();
     cnt=0;
    for(int i=3;i<=2*n+1;i+=2)
    {
        if(col[p[i]]==2) cnt++;
    }
    printf("%d\n",cnt);
    for(int i=3;i<=2*n+1;i+=2)
    {
        if(col[p[i]]==2) printf("%d ",i/2);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值