【题目链接】
ybt 1861:【10NOIP提高组】关押罪犯
洛谷 P1525 [NOIP 2010 提高组] 关押罪犯
【题目考点】
1. 图论:二分图
2. 二分答案
3. 种类并查集
【解题思路】
解法1:种类并查集
一个囚犯是一个顶点,一个囚犯对可以看做一条边,每个囚犯对的怨气值是边的权值。
一个监狱的囚犯属于一个集合,也就是一个连通分量。
现在给定很多顶点和预设的边,将所有的顶点分为两个集合,每个集合中顶点之间的边都生效。求所有的集合划分方案中集合中最大边权的最小值。
为了使集合中边权最大的边最小,可以将所有边按权值从大到小排序。按权值从大到小遍历所有的边,尽量让权值大的边所连接的两个顶点不在同一个集合。如果发现某条边连接的两个顶点无法放在不同的集合,那么该边的两个顶点就必须放在相同的集合中,该边就是集合中边权最大的边。该边的边权就是所有的集合划分方案中集合中最大边权的最小值。
对于第 i i i个顶点,我们可以假想存在顶点 i + n i+n i+n,顶点 i + n i+n i+n与顶点 i i i不在同一个集合。
对于权值为 w w w的边 ( u , v ) (u, v) (u,v)
-
如果顶点 u u u和顶点 v v v已经在同一集合,那么已经无法将 u u u和 v v v放到两个不同的集合,集合中存在的最大边权就是 w w w, w w w就是该问题的结果。
-
否则,让顶点 u u u和顶点 v v v不在同一个集合。由于顶点 v + n v+n v+n和顶点 v v v不在同一集合,那么顶点 u u u和顶点 v + n v+n v+n一定在同一集合,将二者所在的集合合并。
同理,顶点 v v v和顶点 u + n u+n u+n在同一集合,将二者所在的集合合并。
设排序后,有顶点1到顶点2的边,权值为3。有顶点1到顶点3的边,权值为3,有顶点2到顶点3的边,权值为2,设总顶点数n为10。
运行上述算法
- 访问顶点1到顶点2的边,将顶点1和假想的与顶点2不在同一集合的顶点12合并,顶点2和假想的与顶点1不在同一集合的顶点11合并。
- 访问顶点1到顶点3的边,将顶点1和顶点13合并,此时1, 12, 13处于同一集合。将订单3和顶点11合并,此时2, 3, 11处于同一集合。
- 访问顶点2到顶点3的边,发现顶点2、3在同一集合,那么集合中最大边权为顶点2到顶点3的边的权值,为2。
解法2:二分答案+二分图判定
市长看到的冲突事件是影响力最大的冲突事件。要使影响力最大的冲突事件的影响力最小,是最大值最小问题,可以使用二分答案解决。
- 答案变量:集合中的最大边权c
- 最值:求最小值
- 满足条件:将所有顶点分为两个集合,集合内顶点间的边权都小于等于c
该条件仍然难以判断。可以反向思考,如果集合内顶点间的边权都小于等于c,那么边权大于c的边一定只能是分别连接两集合中的两个顶点。
如果只考虑边权大于c的边,所有的边的一端是一个集合中的顶点,另一端是另一个集合中的顶点,该图就是一个二分图。
因此二分答案满足条件为:只考虑边权大于c的边时,该图是二分图。
可以使用染色法进行二分图判定,只有边权大于c时才通过该边访问邻接点。
可以直接二分答案边权数值。也可以先对保存边权的序列进行排序,而后在边权序列上进行二分查找目标边权的下标。
【题解代码】
解法1:种类并查集
#include<bits/stdc++.h>
using namespace std;
#define N 20005
#define M 100005
struct Edge
{int u, v, w;bool operator < (const Edge &b) const{return w > b.w;}
} e[M];
int n, m, fa[2*N], ans;
void initFa(int n)
{for(int i = 1; i <= 2*n; ++i)fa[i] = i;
}
int find(int x)
{return fa[x] = x == fa[x] ? x : find(fa[x]);
}
void merge(int x, int y)
{fa[find(x)] = find(y);
}
int main()
{cin >> n >> m;initFa(n);for(int i = 1; i <= m; ++i)cin >> e[i].u >> e[i].v >> e[i].w;sort(e+1, e+1+m);for(int i = 1; i <= m; ++i){int u = e[i].u, v = e[i].v, w = e[i].w;if(find(u) == find(v)){ans = w;break;}merge(u, v+n);merge(v, u+n);}cout << ans;return 0;
}
解法2:二分答案+二分图判定
- 写法1:广搜二分图判定,二分答案边权数值
#include<bits/stdc++.h>
using namespace std;
#define N 20005
struct Edge
{int v, w;
};
int n, m, color[N];
vector<Edge> edge[N];
bool bfs(int sv, int c)//二分图判定,只关注大于c的边
{queue<int> que;color[sv] = 1;que.push(sv);while(!que.empty()){int u = que.front();que.pop();for(Edge e : edge[u]) if(e.w > c)//只访问权值大于c的边 {int v = e.v;if(color[v] == 0){color[v] = 3-color[u];que.push(v);}else if(color[v] == color[u])return false;}}return true;
}
bool check(int c)//只关注大于c的边时,该图是否为二分图
{memset(color, 0, sizeof(color));for(int i = 1; i <= n; ++i) if(color[i] == 0 && !bfs(i, c))return false;return true;
}
int main()
{int a, b, c;cin >> n >> m;for(int i = 1; i <= m; ++i){cin >> a >> b >> c;edge[a].push_back(Edge{b, c});edge[b].push_back(Edge{a, c});}int l = 0, r = 1e9;while(l < r){int mid = (l+r)/2;if(check(mid))r = mid;elsel = mid+1;}cout << l;return 0;
}
- 写法2:深搜二分图判定,二分查找下标
#include<bits/stdc++.h>
using namespace std;
#define N 20005
#define M 100005
struct Edge
{int v, w;
};
int n, m, color[N], w[M];
vector<Edge> edge[N];
bool dfs(int u, int c)
{for(Edge e : edge[u]) if(e.w > c){int v = e.v;if(color[v] == 0){color[v] = 3-color[u];if(!dfs(v, c))return false;}else if(color[v] == color[u])return false;}return true;
}
bool check(int c)//只关注大于c的边时,该图是否为二分图
{memset(color, 0, sizeof(color));for(int i = 1; i <= n; ++i) if(color[i] == 0){color[i] = 1;if(!dfs(i, c))return false;}return true;
}
int main()
{int a, b, c;cin >> n >> m;for(int i = 1; i <= m; ++i){cin >> a >> b >> c;edge[a].push_back(Edge{b, c});edge[b].push_back(Edge{a, c});w[i] = c;}sort(w+1, w+1+m);int l = 0, r = m;//l最小应该为0,w[0]为0,当下标取到0时,结果为0,此时取所有的边,仍然是二分图。 while(l <= r){int mid = (l+r)/2;if(check(w[mid]))r = mid-1;elsel = mid+1;}cout << w[l];return 0;
}