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

来源:互联网 发布:韩美林 知乎 编辑:程序博客网 时间:2024/06/07 20:17

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;}
原创粉丝点击