这题我采用的方法是二分答案,二分边的长度D,
注意左边界是0开始的,因为可能不会发生冲突。
然后难点就是check函数判断相应的长度是否可行,
我的思路是这样的:
如果可行的话,那么长度大于D的边两个节点,肯定会被分在两个集合里面,
从而想到了二分图:新建一个图,只保存原图中大于D的边,然后判断是否是二分图即可。
二分图判断可以用深搜染色,效率是O(|V|+|E|)
我用的是并查集,如果A和B要相连的话,那么连接A和B',以及B和A'。
把所有的边处理,然后假如A和A'相连的话,那么就一定不是二分图,
二分图的数学定义:而且仅当图中不存在奇环,如果A和A'相连的话,那么肯定存在奇环的。
详见代码:
*************************************
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#define pii pair<int,int>
#define MAXE 100005
#define MAXV 20005
using namespace std;
struct edge{
int u,v;
int c;
};
int V,E;
int D;
int f[MAXV*2];
edge e[MAXE];
int find(int x){
return (f[x]==x)?x:(f[x]=find(f[x]));
}
void init_find(){
for(int i=1;i<=(V<<1);i++){
f[i]=i;
}
}
void lik(int x,int y){
x=find(x),y=find(y);
if(x!=y){
f[x]=y;
}
}
bool same(int x,int y){
return (find(x)==find(y));
}
int check(){
init_find();
int i;
for(i=E;i>=1;i--){
if(e[i].c<=D){
break;
}
lik(e[i].v,e[i].u+V);
lik(e[i].u,e[i].v+V);
}
for(int i=1;i<=V;i++){
if(same(i,i+V)){
return 0;
}
}
return 1;
}
bool comp(const edge &e1,const edge &e2){
return (e1.c<e2.c);
}
int main()
{
// freopen("data.in","r",stdin);
scanf("%d%d",&V,&E);
for(int i=1;i<=E;i++){
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
}
sort(e+1,e+E+1,comp);
// for(int i=1;i<=E;i++){
// printf("%d %d %d\n",e[i].u,e[i].v,e[i].c);
// }
int l=0,r=E;
while(l!=r){
int mid=l+(r-l)/2;
D=e[mid].c;
if(check()){
r=mid;
}
else{
l=mid+1;
}
}
printf("%d",e[r].c);
return 0;
}
**************************************************
我的算法复杂度是|E|log|E|,这里可以过,但是有更为简洁的并查集写法。
这些写法的共同点:
运用贪心的思想,进行从大到小地排序,很大地提高了效率。
主要有三种写法:
(1)朴素并查集
思路是:先排序边的权值,按从大到小的顺序处理,把每个节点所有的敌人用并查集连起来,形成“两大阵营”。
当发现边的两个节点在一个阵营里面时,那么冲突肯定不可避免了,而且此冲突值是最优解,因为是边的权值是从大到小的。
附上代码:
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#define MAXV 20005
#define MAXE 100005
using namespace std;
struct edge{
int u,v,c;
};
edge e[MAXE];
int V,E;
int f[MAXV],b[MAXV];
int find(int x){
return (f[x]==x)?x:(f[x]=find(f[x]));
}
void init_find(){
for(int i=1;i<=V*2;i++){
f[i]=i;
}
}
void lik(int u,int v){
u=find(u),v=find(v);
if(u!=v){
f[u]=v;
}
}
bool same(int u,int v){
return (find(u)==find(v));
}
bool comp(const edge &e1,const edge &e2){
return (e1.c>e2.c);
}
int main()
{
// freopen("data.in","r",stdin);
scanf("%d%d",&V,&E);
init_find();
for(int i=1;i<=E;i++){
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
}
sort(e+1,e+E+1,comp);
for(int i=1;i<=E;i++){
int u=e[i].u,v=e[i].v,c=e[i].c;
if(same(u,v)){
printf("%d\n",c);
return 0;
}
if(!b[u]) b[u]=v;
else lik(v,b[u]);
if(!b[v]) b[v]=u;
else lik(u,b[v]);
}
printf("0\n");
return 0;
}
(2)加权并查集
用加权的方式,记录节点到根的距离,那么可以推出:
如果两个节点连通,且到根的距离都为奇数,或者都为偶数,那么肯定是同一个监狱的,肯定会起冲突。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#define MAXV 20005
#define MAXE 100005
using namespace std;
struct edge{
int u,v,c;
};
edge e[MAXE];
int V,E;
int f[MAXV],w[MAXV];
void init_find(){
for(int i=1;i<=V;i++){
f[i]=i;
}
}
int find(int x){
if(x!=f[x]){
int t=f[x];
f[x]=find(f[x]);
w[x]+=w[t]; //注意这里,根节点到自己的距离是0,所以可以加
}
return f[x];
}
bool comp(const edge &e1,const edge &e2){
return (e1.c>e2.c);
}
int main()
{
// freopen("data.in","r",stdin);
scanf("%d%d",&V,&E);
init_find();
for(int i=1;i<=E;i++){
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
}
sort(e+1,e+E+1,comp);
for(int i=1;i<=E;i++){
int u=e[i].u,v=e[i].v,c=e[i].c;
int x=find(u),y=find(v);
if(x==y){
if((w[u]&1)==(w[v]&1)){
printf("%d\n",c);
return 0;
}
}
else{
f[x]=y;
if((w[u]&1)==(w[v]&1)){
w[x]=1; //为了防止连接之后出现都为奇数或者都为偶数的情况,给将要连上的原先根节点权值1
}
}
}
printf("0\n");
return 0;
}
(3)镜像并查集
如果v和u相连,那么令v与u+n相连,u与v+n相连。
表示的含义是v与u不在同一个阵营。
这种巧妙的方法恰好形成了两个部分,与题中两大阵营对应。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#define MAXV 20005*2
#define MAXE 100005
using namespace std;
struct edge{
int u,v,c;
};
edge e[MAXE];
int V,E;
int f[MAXV];
int find(int x){
return (f[x]==x)?x:(f[x]=find(f[x]));
}
void init_find(){
for(int i=1;i<=V*2;i++){
f[i]=i;
}
}
void lik(int u,int v){
u=find(u),v=find(v);
if(u!=v){
f[u]=v;
}
}
bool same(int u,int v){
return (find(u)==find(v));
}
bool comp(const edge &e1,const edge &e2){
return (e1.c>e2.c);
}
int main()
{
// freopen("data.in","r",stdin);
scanf("%d%d",&V,&E);
init_find();
for(int i=1;i<=E;i++){
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
}
sort(e+1,e+E+1,comp);
for(int i=1;i<=E;i++){
int u=e[i].u,v=e[i].v,c=e[i].c;
if(same(u,v)){
printf("%d\n",c);
return 0;
}
lik(u,v+V);
lik(v,u+V);
}
printf("0\n");
return 0;
}
一般来说,凭我的感觉。。。加权并查集和镜像并查集是可以互相转化的,比如说是食物链
********************************************************
总之,后来想了想,还是并查集更加合适,这题是训练并查集的一道经典题