题解:洛谷 P5543 The Great Revegetation
【题目来源】
洛谷:P5543 [USACO19FEB] The Great Revegetation S - 洛谷
【题目描述】
一场漫长的干旱使农场主约翰的 \(N\) 个牧场没有草。然而,随着雨季的到来,是时候“重新种植”了。在农夫约翰的小屋里,他有两个桶,每个桶都有不同类型的草籽。他想在他的每一个牧场种草,在每一个牧场中选择一种类型的草。
作为一名奶农,农场主约翰想确保他能满足他那几头奶牛的特殊饮食需求。他的 \(m\) 头奶牛都有两个最喜欢的牧场。他的一些奶牛有一个饮食限制,那就是他们应该只吃一种类型的草,因此农场主约翰希望确保在这类奶牛最喜欢的两个田里种植同一种类型的草。其他的奶牛有一个不同的饮食限制,要求他们吃不同类型的草。对于那些奶牛,农场主约翰当然想确保他们最喜欢的两块田地里有不同的草。
请帮助农场主约翰确定他在他的 \(N\) 个牧场上种植草的不同方式的数量。
【输入】
输入的第一行包含两个整数 \(N\)(\(2\le N\le10^5\))和 \(m\)(\(1\le m\le10^5\))。
之后 \(m\) 行,每行描述了一头奶牛。首先是一个字符 S 或 D,表示这头奶牛需要相同(S)还是不同(D)的草类型,然后是两个 \(1\sim N\) 的整数,表示这头奶牛喜欢的两块牧场。
【输出】
输出农场主约翰在他的 \(N\) 个牧场上植草的方式总数。请用二进制写你的答案。
【输入样例】
3 2
S 1 2
D 3 2
【输出样例】
10
【核心思想】
-
问题分析:给定 \(N\) 个牧场和 \(m\) 头奶牛的约束条件,每头奶牛要求两个牧场种植相同类型(
S)或不同类型(D)的草,求满足所有约束的种植方案数。这是一个扩展二分图染色问题,关键在于处理两种约束关系并判断是否存在合法染色方案。 -
算法选择:
- BFS扩展染色:使用BFS遍历每个连通分量,根据约束关系进行染色
- 相同关系邻接表
s:存储要求相同颜色的边 - 不同关系邻接表
d:存储要求不同颜色的边 - 颜色数组
color:用1和-1表示两种颜色 - 矛盾检测:在染色过程中检测是否出现矛盾(相同关系颜色不同,或不同关系颜色相同)
-
关键步骤:
- 初始化:读取 \(N\)(牧场数)和 \(m\)(奶牛数),建立相同关系邻接表
s和不同关系邻接表d - 遍历所有连通分量:
- 对于每个未访问的节点,启动BFS进行染色
- BFS染色过程:
- 将起始节点染为颜色
1 - 遍历相同关系邻居:要求颜色相同,若已访问且颜色不同则出现矛盾
- 遍历不同关系邻居:要求颜色不同,若已访问且颜色相同则出现矛盾
- 将未访问的邻居入队并染色
- 将起始节点染为颜色
- 统计连通分量数:每处理一个连通分量,答案字符串后添加
"0" - 输出结果:答案为 \(2^{\text{连通分量数}}\),用二进制表示即
"1"后面跟连通分量数个"0"
- 初始化:读取 \(N\)(牧场数)和 \(m\)(奶牛数),建立相同关系邻接表
-
时间/空间复杂度:
- 时间复杂度:\(O(N + m)\),每个节点和边只访问一次
- 空间复杂度:\(O(N + m)\),需要存储两个邻接表、访问标记数组、颜色数组
-
扩展二分图染色的核心思想:
- 约束关系建模:将相同关系视为"必须同色",不同关系视为"必须异色"
- BFS染色传播:从起始节点开始,根据约束关系向邻居传播颜色
- 矛盾检测:在染色过程中实时检测约束冲突
- 连通分量独立性:每个连通分量的染色方案相互独立,总方案数为 \(2^{\text{连通分量数}}\)
- 二进制表示:由于每个连通分量有2种染色方案,答案为 \(2^k\),二进制形式为
1后跟 \(k\) 个0 - 适用于带约束的染色问题、2-SAT简化问题
【算法标签】
普及+ #BFS-图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 200005, M = 100005 * 2; // 定义最大节点数和边数
int n, m; // 节点数n,边数m
vector<int> s[N], d[N]; // 相同关系邻接表s,不同关系邻接表d
bool vis[N]; // 访问标记数组
int color[N]; // 颜色标记数组,1表示一种颜色,-1表示另一种颜色
string ans = "1"; // 答案字符串,初始化为"1"
bool flag; // 标记是否出现矛盾
void bfs(int x) // 广度优先搜索进行二分图染色
{
vis[x] = 1; // 标记当前节点已访问
color[x] = 1; // 将当前节点染为颜色1
queue<int> q; // 广度优先搜索队列
q.push(x); // 起始节点入队
while (!q.empty()) // 当队列不为空时循环
{
int u = q.front(); q.pop(); // 取出队首节点
for (int i=0; i<s[u].size(); i++) // 遍历所有相同关系的邻居
{
int v = s[u][i]; // 相同关系邻居节点
if (vis[v]) // 如果邻居节点已访问
{
if (color[v]!=color[u]) // 如果颜色不同,但要求相同,出现矛盾
{
flag = true; // 标记矛盾
return; // 返回
}
}
else // 如果邻居节点未访问
{
vis[v] = 1; // 标记邻居节点已访问
color[v] = color[u]; // 染相同颜色
q.push(v); // 邻居节点入队
}
}
for (int i=0; i<d[u].size(); i++) // 遍历所有不同关系的邻居
{
int v = d[u][i]; // 不同关系邻居节点
if (vis[v]) // 如果邻居节点已访问
{
if (color[v]==color[u]) // 如果颜色相同,但要求不同,出现矛盾
{
flag = true; // 标记矛盾
return; // 返回
}
}
else // 如果邻居节点未访问
{
vis[v] = 1; // 标记邻居节点已访问
color[v] = -color[u]; // 染不同颜色
q.push(v); // 邻居节点入队
}
}
}
}
int main() // 主函数
{
cin >> n >> m; // 输入节点数和边数
while (m--) // 读入所有边
{
char c; // 关系类型
int a, b; // 边的两个端点
cin >> c >> a >> b; // 输入关系类型和节点
if (c=='S') // 如果是相同关系
{
s[a].push_back(b); // 在a的相同关系表中添加b
s[b].push_back(a); // 在b的相同关系表中添加a
}
else // 如果是不同关系
{
d[a].push_back(b); // 在a的不同关系表中添加b
d[b].push_back(a); // 在b的不同关系表中添加a
}
}
for (int i=1; i<=n; i++) // 遍历所有节点
{
if (vis[i]==0) // 如果节点未访问
{
bfs(i); // 对该节点所在的连通分量进行BFS染色
if (flag) // 如果出现矛盾
{
cout << 0 << endl; // 输出0表示无解
return 0; // 程序结束
}
ans += "0"; // 没有矛盾,在答案字符串后添加"0"
}
}
cout << ans << endl; // 输出答案字符串
return 0; // 程序正常结束
}
【运行结果】
3 2
S 1 2
D 3 2
浙公网安备 33010602011771号