2-sat前缀优化建图,看来上一道题也可以算是前缀优化
每个点先拆成两个点,表示这个点选或不选,再额外拆成两个点,表示所在部分这个点以及以前的点有/没有选到的
一条边两边只能选一个点的限制很好处理
每个部分选一个点的情况大力连边会(n^2)
这里就要用到我们之前的前缀部分节点,具体含义代码里说
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define y1 qwq
inline int read()
{
int x=0;char ch,f=1;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') f=0,ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
const int N=4e6+10,inf=1<<30;
int n,m,k;
int head[N],cnt;
struct point
{
int nxt,to;
point(){}
point(const int &nxt,const int &to):nxt(nxt),to(to){}
}a[N<<1];
inline void link(int x,int y)
{
a[++cnt]=(point){head[x],y};head[x]=cnt;
}
int dfn[N],low[N],st[N],col[N];
int idx,top,sum;
inline void tarjan(int now)
{
dfn[now]=low[now]=++idx;
st[++top]=now;
for(int i=head[now];i;i=a[i].nxt)
{
int t=a[i].to;
if(!dfn[t])
{
tarjan(t);
low[now]=min(low[now],low[t]);
}
else if(!col[t]) low[now]=min(low[now],dfn[t]);
}
if(dfn[now]==low[now])
{
col[now]=++sum;
while(st[top]!=now) col[st[top--]]=sum;
--top;
}
}
inline void main()
{
n=read(),m=read(),k=read();
for(int x,y,i=1;i<=m;++i)
{
x=read(),y=read();
link(x+n,y),link(y+n,x);
}
for(int x,y,i=1;i<=k;++i)
{
int sum=read();y=0;
for(int j=1;j<=sum;++j)
{
x=read();
//x+2*n 有过 x+3*n 没有
//x 选了 x+n 不选
if(y)
{
link(y+n*2,x+n*2);
//上一个点有过了,那么到这个点肯定也有了
link(x+3*n,y+3*n);
//这个点还没有,上一个点肯定也还没有
link(x,y+3*n);
//这个点选了,上一个点以及之前肯定没有
link(y+2*n,x+n);
//上一个点有过了,这个点不能选
}
link(x,x+2*n);
//这个点选了,这个点以及之前有过了
link(3*n+x,x+n);
//这个点以及之前没有,这个点不选
y=x;
}
}
for(int i=1;i<=4*n;++i)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;++i)
{
if(col[i]==col[i+n]||col[i+2*n]==col[i+3*n])
{
puts("NIE");
return;
}
}
puts("TAK");
}
}
signed main()
{
//freopen("haha.in","r",stdin);
red::main();
return 0;
}