P6378 题解

题意简述

给出一张 n(1n106) 个点 m(1m106) 条边的无向图。现在对 n 个点黑白染色,满足每条边连接的两点至少有一个是黑点;同时题目将这 n 个点恰好分为 k(1kn) 部分,要求每部分有且只有一个黑点。求是否有满足条件的染色方案。

题目分析

很明显是 2-SAT 问题。如一般 2-SAT 题相同,我们设 x0x 点是白色的状态,x1x 点是黑色的状态。

那么我们对每条边 (x,y),如果 x 是白点,那么 y 一定是黑点。所以我们从 x0y1 连边;同理从 y0x1 连边。

对于每“部分”点内的条件转化,首先可以发现每个“部分”至少一个黑点这一条件是无用的。因为若该部分内有至少两个点之间有连边,那么就至少有一个黑点;否则任意两个点之间都没有连边,随便找一个点定为黑点就行。而每个“部分”内只有至多一个黑点一条件,直接处理是将“部分”中每个点 x 对“部分”中其他所有点 y,从 x1y0 连边。这样做边数是 O(n2) 级别的,空间、时间都会炸。

事实上,对于这种类似于对一个图中某点集内的点互相之间连边的操作,可以考虑前缀优化建图的方式解决。例如本题,以 {1,2,3,5,9} 这一“部分”点中 31 点向其他点连边为例,如图:

这样对一个点暴力建边的复杂度为 O(n)。注意到这些边的终点实际上是这一“部分”中首尾两段点,又考虑到 tarjan 算法本质上只需要维护两点之间的可达性。那么对“部分”中的开头一段,我们就可以采用“前缀”的方式维护可达性。具体来说,对每个 x 点多建立一个 x2 点,然后从 x2x0 连边,接着对每“部分”中的所有点,将 x2 连成一条链。如图所示:

然后我们发现只需要从 3122 连一条边就可以实现从 311020 连边的效果:

对“部分”中结尾一段,也可以类似地采用后缀方式优化。

这样,我们就可以在 4×n 个点和至多 8×n 条边的图上跑 tarjan 解决问题即可。时间复杂度仍是 O(n+m)

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,m,k,w,x,y;
int tot,hd[4000010],v[8000010],nt[8000010];
int cnt,dfn[4000010],low[4000010],stk[4000010]/*栈*/,top,num,c[4000010]/*点所在 SCC 编号*/;
bool ins[4000010];//是否入栈
//用下标为 x 的点代表 x_0,x+n 代表 x_1,x+2*n 代表 x_2,x+3*n 代表 x_3
void rd(int &x)
{
x=0;
char c=getchar();
for(;c>'9'||c<'0';c=getchar());
for(;c<='9'&&c>='0';c=getchar())
x=(x<<3)+(x<<1)+c-'0';
}//10^6 的数据,用个快读
void add(int x,int y)
{
v[++tot]=y;
nt[tot]=hd[x];
hd[x]=tot;
}//建边
void tarjan(int x)//tarjan 求 SCC
{
dfn[x]=low[x]=++cnt;
stk[++top]=x;
ins[x]=1;
for(int i=hd[x];i;i=nt[i])
{
int y=v[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
int y;
num++;
do
{
y=stk[top--];
ins[y]=0;
c[y]=num;
}while(y!=x);
}
}
int main()
{
rd(n),rd(m),rd(k);
for(int i=1;i<=m;i++)
{
rd(x),rd(y);
add(x,y+n);
add(y,x+n);
}
for(int i=1;i<=k;i++)
{
rd(w);
y=0;//上一个点
for(int j=1;j<=w;j++)
{
rd(x);
add(x+2*n,x);
add(x+3*n,x);
if(y)
{
add(x+n,y+2*n);
add(y+n,x+3*n);
add(x+2*n,y+2*n);
add(y+3*n,x+3*n);
}
y=x;
}
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=n;i++)
if(c[i]==c[i+n])//如果 i_0 和 i_1 在同一个 SCC 里面,推出矛盾!
{
printf("NIE");
return 0;
}
printf("TAK");
return 0;
}
posted @   Hadtsti  阅读(26)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示