这道题看到好多dalao用的并查集,看了一下题解看不太懂,还是我太弱,只能用二分图+二分来做。
二分图就是将图中的点分成两边,也就是分成两个集合,使得。每个集合中的点之间不会有边相连接。如果有边相连一定是不同集合点相连,关于二分图的判定也很简单:就是看有没有奇数的环,从中我们也可以得到一个结论,没有边的图一定是二分图。
这就是二分图,看到没,图中两个集合的内部没有边,只有不同集合之间才有边相连接。关于二分图的判定,这里介绍一种方法:~染色法,只有两种颜色:红色和蓝色
我们已一个顶点开始,任何一个顶点都可以。然后将其染成红色,然后遍历其所与之相连的点,如果其是红色,则图不是二分图,直接返回false,如果是蓝色则跳过,如果没有染色,则把这个点染成和与之相连的那个点相反的颜色(蓝色),然后从这个点出发依次遍历其他与之相连的点~一般用dfs实现,代码也很好写。如果遍历的过程中没有返回false,则最后返回true
最理想的情况当然是没有冲突,题目好像有一个样例是没有冲突的。我们可以这样,假设我们找到了一个mid使得这个两个监狱中冲突值最小为mid ,很显然,对于mid较小的时的方案,当mid较大时依然可行,所以这就符合单调性,可以用二分来求解~
我们先设left=0,right为罪犯里面两个罪犯之间的怨气的最大值,然后开始二分.
int mid=(left+right)/2,将此时怨气值大于mid的点连接起来,也就是说这两个罪犯关在不同的监狱,那么小于等于mid的点就不连接,这两个罪犯在一个监狱。这样连接以后,会形成一个图,我们判断这个图是否是二分图.这个图可能是连通图,可能不连通,如果是连通图,我们只需要dfs一次即可,但是图是非连通的我们需要dfs N次(N为顶点的个数),如果这个图是二分图,那么我们就看看还有没有比mid更小的怨气值成立也可以是二分图(right=mid-1),如果不是二分图,说明此时怨气值mid太理想化了,不满足条件,需要看看怨气值大一点是否满足条件,所以就令left=mid+1.
样例的最终答案就是这样的,把大于3512的怨气值连起来。然后判断是否是二分图.很显然是二分图,执行到最后一步算法也就结束了right=mid-1.此时left>right
记住上面是将>mid的连接,不是>=mid的连接,如果是大于等于mid的连接,最终结果要减去1,为什么呢,你仔细再看看上面那个图,等于3512的部分不要连接.
刚开始我也是把大于等于mid的连接,调试后才发现错了.
这是倒数第二步调试,可以看到此时要将大于等于3513的部分连接起来,此时满足二分图.所以顺便保存一下结果res=mid=3513
然后right=mid-1=3512
mid=(left+right)/2=3512,此时将大于等于3512部分连接,不能得到二分图,此时left=mid+1=3513>right,所以不满足条件了,那么我们的res就是3513,要减去1才是最终结果.但是有一种情况减1也是错的,如果此时的阵容可以满足怨气值为0,再减去1,那就会出错了.就比如下面这样
所以我们还是换一种安全的做法,当大于mid的时候,我们才连接两个罪犯.这样上面样例最后一步就是mid=3512,此时连接怨气值大于3512的两个罪犯,然后判断其是否是二分图.满组条件.right=mid-1<left,满足条件退出循环~
下面是AC代码~
#include<iostream>
#include <vector>
#include <cstring>
using namespace std;
#define Max 100000
struct Node
{
int v2;
int weight;
};
vector<Node> graph[Max];
vector<Node> g[Max];
int Max_val=-1;
int color[Max];
bool solve(int N);
bool dfs(int v,int col);
int res=0;
int main()
{
int N,M;
cin>>N>>M;
int v1,v2,val;
for(int i=1;i<=M;i++)
{
cin>>v1>>v2>>val;
Node node{v2,val};
graph[v1].push_back(node);
Max_val=max(Max_val,val);
}
int left=0,right=Max_val;
while(left<=right)
{
//假设此时mid是最大的二分边界
int mid=(left+right)/2;
for(int i=1;i<=N;i++)
{
g[i].clear();
}
for(int i=1;i<=N;i++)
{
if(graph[i].size()!=0)
{
for(int j=0;j<graph[i].size();j++)
{
if(graph[i][j].weight>mid)//大于mid的罪犯之间连一条边
{
Node node{graph[i][j].v2,graph[i][j].weight};
g[i].push_back(node);
Node nodes{i,graph[i][j].weight};
g[graph[i][j].v2].push_back(nodes);
}
}
}
}
memset(color,0, sizeof(color));
if(solve(N))
{
right=mid-1;
res=mid;
}
else{
left=mid+1;
}
}
cout<<res<<endl;
return 0;
}
bool dfs(int v,int col)
{
color[v]=col;
for(int i=0;i<g[v].size();i++)
{
if(color[g[v][i].v2]==col)//染上了相同的颜色
{
return false;
}
if(color[g[v][i].v2]==0&&!dfs(g[v][i].v2,-col)) //没有涂色,并且以v2为起点染色失败
{
return false;
}
}
return true;
}
bool solve(int N) //判断是否是二分图
{
for(int i=1;i<=N;i++) //以i为顶点开始染色
{
if(color[i]==0) //没有被染色
{
if (!dfs(i,2)) //开始以i为顶点染色 成2
{
return false;
}
}
}
return true;
}