P11022 「LAOI-6」Yet Another Graph Coloration Problem 解题报告


P11022 「LAOI-6」Yet Another Graph Coloration Problem 解题报告

1. 题目核心思想解读

首先,我们来弄懂题目到底要求我们做什么。题目要求我们将图中的节点染成黑白两色,同时满足三个条件:

  1. 至少有一个黑点。
  2. 至少有一个白点。
  3. 任意一个黑点 u 和一个白点 v 之间,都必须有至少两条不同的简单路径

前两个条件很简单,只要我们不把所有点染成同一种颜色就行。关键在于第三个条件。

“两点之间有至少两条路径”,在图论中,这是一个非常强烈的信号,它通常和环(Cycle)或者双连通分量有关。

想象一下,如果两点 uv 在一个环上,那么从 uv 天然就有两条路径:顺时针走和逆时针走。这个性质启发我们,问题的解很可能和图中的环有关

如果一个图没有环,它就是一棵树(或者森林,即多个不连通的树)。在树上,任意两点之间的路径是唯一的。因此,如果图是一棵树,我们无论如何染色,都无法满足黑白点之间有两条路径的要求。所以,如果给定的图是树或森林,一定无解

2. 解题策略:寻找一个“环”并利用它

既然“环”是关键,我们的策略就是找到图中的一个环,并利用它来构造一种合法的染色方案。

怎么找环呢?一个经典的方法是深度优先搜索(DFS)

  1. 构建 DFS 树:我们从任意一个节点(比如 1 号点)开始进行 DFS 遍历。所有通过 DFS 访问边走过的路径会构成一棵生成树,我们称之为 “DFS 树”。
  2. 寻找返祖边:在 DFS 过程中,如果当前节点 u 遇到一个邻居 v,而 v 之前已经被访问过,并且 v 不是 u 的父节点,那么边 (u, v) 就是一条返祖边。这条返祖边 (u, v) 和 DFS 树上从 vu 的路径一起,构成了一个

(上图中,实线是 DFS 树的边,虚线 (x, p) 是一条返祖边)

找到了一个环,我们就可以构造染色方案了!题解给出了一个非常巧妙的构造方法:

  • 假设我们找到了一条返祖边 (x, p),其中 x 在 DFS 树中的深度比 p 更深。
  • 我们将节点 x 以及它在 DFS 树中的所有子孙节点(即 x 的子树)全部染成黑色
  • 将图中的其余所有节点染成白色

3. 为什么这个构造是正确的?

我们来验证一下这个染色方案是否满足“任意黑白点对之间都有两条路径”。

b 是任意一个黑点,w 是任意一个白点。根据我们的构造,b 肯定在 x 的子树里,而 w 在子树外。

  • 路径一(树上路径)
    由于整个图是连通的,在 DFS 树上,bw 之间必然存在一条唯一的路径。这条路径完全由树边构成。

  • 路径二(绕行返祖边)

    1. 从黑点 b 出发,沿着 DFS 树向上走到 x
    2. 通过返祖边 (x, p),我们从 x 直接跳到了它的祖先 p
    3. 因为 px 的祖先,所以 p 不在 x 的子树里,因此 p 是一个白点。
    4. 现在我们位于白点 p,目标是另一个白点 w。我们只需沿着 DFS 树的路径从 p 走到 w 即可。

这条新路径 b → ... → x → p → ... → w 使用了返祖边 (x, p),而路径一完全没有使用它。因此,这两条路径必然是不同的。这样,我们就为任意一对黑白点找到了两条不同的简单路径。

所以,只要我们能找到至少一条返祖边,就意味着图中有环,我们就能用上述方法构造出一种合法的解。

4. 无解的情况

根据上面的分析,我们可以总结出无解的情况:

  1. 图不连通:如果图不连通,我们把一个连通块染成黑色,另一个染成白色,那么黑白点之间根本没有路径,不满足条件。如果我们只在一个连通块内染色,那其他连通块的点颜色怎么算?为了满足“至少一个黑点”和“至少一个白点”的条件,我们必须把颜色分布在整个图中,这在不连通图上是无法满足“两条路径”条件的。
  2. 图连通但没有环(即图是一棵树):如前所述,树上任意两点路径唯一,无法满足条件。

一个连通的图没有环,等价于它没有返祖边。所以,无解的充要条件就是:图不连通,或者图中没有环

5. 代码实现解析

题解中提供的代码正是实现了上述思路,它通过两次 DFS 来完成任务。

#include<bits/stdc++.h>
// ... (头文件和定义)

vector<int>q[200005]; // 邻接表存图
int vis[200005];     // 访问标记数组
int qwq[200005];     // 多功能标记数组,下面解释
int ans;             // 标记是否找到了返祖边(即是否有环)

// 第一次DFS:检查连通性和寻找返祖边
void inline dfs(int x,int fa){
   vis[x]=1; // 标记x已访问
   for(int i=0;i<q[x].size();i++){
       int v = q[x][i];
       if(v==fa) continue; // 忽略到父节点的边
       if(vis[v]){ // 如果邻居v已经被访问过,且不是父节点
           if(ans==0) { // 找到第一条返祖边
               ans=1;      // 标记我们找到了环
               qwq[x]=1;   // 标记x是某条返祖边的“深端”点
           }
       } else { // 如果邻居v未被访问
           dfs(v,x); // 继续DFS
       }
   }
}

// 第二次DFS:根据第一次的结果进行染色
void inline dfs2(int x, int y){
    vis[x]=1; // 再次使用vis数组,标记已访问
    // y表示x的祖先中,是否有被标记为“深端”的点
    // qwq[x]继承自身或祖先的标记
    qwq[x]=max(qwq[x], y); 
    for(int i=0;i<q[x].size();i++){
        int v = q[x][i];
        if(!vis[v]) {
            // 将标记(qwq[x])传递给子节点
            dfs2(v, max(y, qwq[x])); 
        }
    }
}

int main(){
   cin>>t;
   while(t--){
       // 初始化
       cin>>n>>m;
       ans=0;
       for(int i=1;i<=n;i++) q[i].clear(), vis[i]=0, qwq[i]=0;
       // 读入图
       for(int i=1;i<=m;i++){ cin>>u>>v; q[u].push_back(v); q[v].push_back(u); }

       // 1. 运行第一次DFS
       dfs(1,0);
       
       // 2. 检查无解情况
       bool opt=false;
       for(int i=1;i<=n;i++) {
           if(vis[i]==0) { // 如果有节点没被访问到,说明图不连通
               opt=1;
               cout<<-1<<"\n";
               break;
           }
       }
       if(opt) continue;
       if(ans==0){ // 如果ans仍为0,说明没找到返祖边,图是树
           cout<<-1<<"\n";
           continue;
       }

       // 3. 有解,运行第二次DFS进行染色
       for(int i=1;i<=n;i++) vis[i]=0; // 重置vis数组
       dfs2(1,0);

       // 4. 输出结果
       for(int i=1;i<=n;i++){
           if(qwq[i]) cout<<"B"; // qwq[i]为1的点是黑色
           else cout<<"W";      // 否则是白色
       }
       cout<<"\n";
   }
}

代码逻辑梳理

  1. dfs 函数

    • 从节点 1 开始遍历图。
    • vis 数组用于判断节点是否已访问。
    • 当发现一个已访问的邻居 v(且不是父节点)时,就找到了一个环。ans=1 记录下这个事实。
    • qwq[x]=1 标记当前节点 x 是我们找到的环的一部分(具体来说,是返祖边的那个深度更深的点)。
    • dfs 结束后,检查 vis 数组可以判断图是否连通。检查 ans 变量可以判断图是否有环
  2. dfs2 函数

    • 这个函数的目的是实现染色。我们的染色策略是“将 x 的子树染黑”。
    • 代码的实现比这个策略更通用一些:它将所有在第一次 dfs 中被 qwq 标记为 1 的节点,以及这些节点的所有后代,都染成黑色。
    • dfs2(x, y) 中的 y 参数起到了一个“继承”的作用。如果一个节点的祖先 p 被标记了(qwq[p]=1),那么这个标记会通过 y 参数一路传递给 p 的所有子孙。
    • 最终,qwq[i] == 1 的所有节点 i 就构成了我们的黑色节点集合,其余为白色。

6. 总结

本题的核心是将一个看似复杂的图论问题,转化为一个经典且直观的子问题:判断图中是否存在环

  • 解题步骤
    1. 通过 DFS 判断图是否连通且有环。
    2. 如果图不连通或是树,则无解,输出 -1
    3. 如果有环,则存在返祖边。任选一条返祖边 (x, p)xp 的后代)。
    4. x 及其在 DFS 树中的子树染成黑色,其余染成白色,这便是一种合法的构造方案。
    5. 代码通过两次 DFS 高效地实现了这个判断和构造过程。

这个思路清晰地展示了如何利用图的基本性质(环、DFS树、返祖边)来解决复杂约束下的构造问题。

posted @ 2025-07-14 14:34  surprise_ying  阅读(14)  评论(0)    收藏  举报