先%下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 fake
for(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 down
sccc::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->yes
inline int crm(int x,int p)
{return x+p*n;}
// sentence 0->real 1->fake
inline 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 real
add( stc(pre[u],1),crm(v,val) );
//opposite to before
add( stc(i,0),crm(v,val) );
//if the sentence and those before are real the man he refer to must match his sentence
add( crm(v,val^1),stc(i,1) );
//opposite to before
add( stc(i,0),stc(pre[u],0) );
//if the sentence and those before are real those before must be real
add( stc(pre[u],1),stc(i,1) );
//opposite to before
pre[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;
}