P6378 题解

题意简述

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

题目分析

很明显是 2-SAT 问题。如一般 2-SAT 题相同,我们设 $x_0$ 为 $x$ 点是白色的状态,$x_1$ 为 $x$ 点是黑色的状态。

那么我们对每条边 $(x,y)$,如果 $x$ 是白点,那么 $y$ 一定是黑点。所以我们从 $x_0$ 向 $y_1$ 连边;同理从 $y_0$ 向 $x_1$ 连边。

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

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

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

然后我们发现只需要从 $3_1$ 向 $2_2$ 连一条边就可以实现从 $3_1$ 到 $1_0$ 与 $2_0$ 连边的效果:

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

这样,我们就可以在 $4\times n$ 个点和至多 $8\times 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 @ 2023-09-02 23:15  Hadtsti  阅读(22)  评论(0编辑  收藏  举报  来源