UOJ #210. 【UER #6】寻找罪犯 2-sat 前缀优化建边 详解
来源:互联网 发布:小提琴制作材料淘宝 编辑:程序博客网 时间:2024/05/21 10:51
先%下Flaze_ 太强了Orz
这是一个2-sat
2-sat 边 u->v 的含义在于:若u则一定v
要诀就在于一定要对每一个这样的约束条件考虑完全
先来一个40分做法
裸的2-sat ~
常识两排点 表示i是or不是犯人
我们考虑:
若u不是罪犯:
则u说的都是真话,谈到的所有人身份都将确定
则所有说u是罪犯都是罪犯
若u是罪犯:
则u可能有一句话是假的,那么枚举这句假话,剩下的就都是真的
这样建图 跑2-sat就可以得到答案了
至于怎么求方案
就看其他的blog吧。
#include<cmath>#include<ctime>#include<cstdio>#include<cstring>#include<cstdlib>#include<iostream>#include<algorithm>#include<iomanip>#include<vector>#include<string>#include<bitset>#include<queue>#include<set>#include<map>using namespace std;typedef double db;typedef long long ll;inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}return x*f;}void print(int x){if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}const int N=200100;struct EDGE{int to,nt,val;};int n,m;namespace sccc{EDGE e[30000000];int last[N],ecnt;inline void add(int u,int v){e[++ecnt]=(EDGE){v,last[u]};last[u]=ecnt;}int dfn[N],low[N],tim;int bel[N],size[N],scc;bool ins[N];int st[N],top;void tarjan(int u){dfn[u]=low[u]=++tim;st[++top]=u;ins[u]=1;for(int i=last[u],v;i;i=e[i].nt){v=e[i].to;if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);else if(ins[v]&&dfn[v]<low[u])low[u]=dfn[v];}if(dfn[u]==low[u]){int tmp;scc++;do{tmp=st[top--];ins[tmp]=0;bel[tmp]=scc;size[scc]++;}while(tmp!=u);}}}EDGE e[N];int last[N],ecnt;inline void add(int u,int v,int val){e[++ecnt]=(EDGE){v,last[u],val};last[u]=ecnt;}int main(){n=read();m=read();register int i,j,u,v,val;for(i=1;i<=m;++i){u=read();v=read();val=read();add(u,v,val^1);}// 1-n real n+1-2n fakefor(u=1;u<=n;++u){for(i=last[u];i;i=e[i].nt){sccc::add(u,e[i].to+(e[i].val)*n);//if u is real v's identity will get downsccc::add(e[i].to+(1^e[i].val)*n,u+n);//if v cannot match u u must be fake}for(i=last[u];i;i=e[i].nt)for(j=e[i].nt;j;j=e[j].nt){sccc::add(e[i].to+(1^e[i].val)*n,e[j].to+e[j].val*n);//if e[i].to cannot match u e[j].to will surely match u sccc::add(e[j].to+(1^e[j].val)*n,e[i].to+e[i].val*n);//ditto}}for(i=1;i<=n<<1;++i)if(!sccc::dfn[i])sccc::tarjan(i);int num=0;for(i=1;i<=n;++i)if(sccc::bel[i]==sccc::bel[i+n]){puts("Impossible");return 0;}else if(sccc::bel[i]>sccc::bel[i+n]) num++;cout<<num<<endl;for(i=1;i<=n;++i)if(sccc::bel[i]>sccc::bel[i+n])print(i),putchar(' ');putchar('\n');return 0;}
之后是全分做法
之所以炸掉是因为边数太多
使用 前缀建边优化
我们加两排点到原来的后边
表示说这句话的人在这句话之前(包括这句话)说的话是不是都是真话
我们记这个东东为stc
所以每一条供词加入时的情况就变成了:
若这个人不是罪犯:
则之前的stc都为真,所指向的人身份正确
若这个人是罪犯(这句话为假):
则这个人之前的stc都为真,自己及之后的都为假,这个人一定是犯人,所指向的人的身份错误
若stc为真:
则指向的人身份确定,这个人之前的stc都为真
若之前stc为假:
则stc为假,这句一定为真,所指向的人身份正确
这样就可以完成所有的约束
#include<cmath>#include<ctime>#include<cstdio>#include<cstring>#include<cstdlib>#include<iostream>#include<algorithm>#include<iomanip>#include<vector>#include<string>#include<bitset>#include<queue>#include<set>#include<map>using namespace std;typedef double db;typedef long long ll;inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}return x*f;}void print(int x){if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}const int N=400100;int last[N<<1],ecnt;struct EDGE{int to,nt;}e[N<<3];inline void add(int u,int v){e[++ecnt]=(EDGE){v,last[u]};last[u]=ecnt;}int n,m;int dfn[N],low[N],tim;int bel[N],size[N],scc;bool ins[N];int st[N],top;void tarjan(int u){dfn[u]=low[u]=++tim;st[++top]=u;ins[u]=1;for(int i=last[u],v;i;i=e[i].nt){v=e[i].to;if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);else if(ins[v]&&dfn[v]<low[u])low[u]=dfn[v];}if(dfn[u]==low[u]){int tmp;scc++;do{tmp=st[top--];ins[tmp]=0;bel[tmp]=scc;size[scc]++;}while(tmp!=u);}}// criminal 0->not 1->yesinline int crm(int x,int p){return x+p*n;}// sentence 0->real 1->fakeinline int stc(int x,int p){return (n<<1)+x+p*m;}int pre[N];int main(){n=read();m=read();register int i,u,v,val;for(i=1;i<=n;++i) pre[i]=2*m+1;for(i=1;i<=m;++i){u=read();v=read();val=read()^1;add( crm(v,val^1),stc(pre[u],0) );//if this sentence is fake those before must be realadd( stc(pre[u],1),crm(v,val) );//opposite to beforeadd( stc(i,0),crm(v,val) );//if the sentence and those before are real the man he refer to must match his sentenceadd( crm(v,val^1),stc(i,1) );//opposite to beforeadd( stc(i,0),stc(pre[u],0) );//if the sentence and those before are real those before must be realadd( stc(pre[u],1),stc(i,1) );//opposite to beforepre[u]=i;}for(i=1;i<=n;++i)add( stc(pre[i],1),crm(i,1) ),add( crm(i,0),stc(pre[i],0) );for(i=1;i<=(m+n)<<1;++i)if(!dfn[i])tarjan(i);int num=0;for(i=1;i<=n;++i)if(bel[i]==bel[i+n]){puts("Impossible");return 0;}else if(bel[i]>bel[i+n]) num++;cout<<num<<endl;for(i=1;i<=n;++i)if(bel[i]>bel[i+n])print(i),putchar(' ');putchar('\n');return 0;}
阅读全文