您的位置:首页 > 其它

洛谷Oj-P1525 关押罪犯-二分答案+二分图判定

2018-03-15 09:51 281 查看
问题描述:

S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c 的冲突事件。

每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。

在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。

那么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?

AC代码:

struct edge
{
int to;
int next;
int w;
};
edge e[200010];//因为是无向图,所以数组要开大一倍
int n,m;
int cnt = 1;
int head[20010];
void add_edge(int u,int v,int w)
{
e[cnt].to = v;
e[cnt].next = head[u];
e[cnt].w = w;
head[u] = cnt;
cnt++;
}
//限制?删边?BFS判定二分图完毕后再加回来?不用!太麻烦!只需:
bool check_bfs(int max_i)//在限制条件下的二分图判定
{
int color[20010];
memset(color,0,sizeof(color));//初始化
queue<int> q;
for(int i = 1; i <= n; ++i)//对于每一个顶点(一般情况下是防止图不连通的情况)
{
if(color[i] == 0)//如果没有颜色
{
q.push(i);//入队
color[i] = 1;//染色(颜色只有1、2两种)
}
else//已经有颜色了
continue;
while(!q.empty())//找连通块
{
int from = q.front();//起点
q.pop();//出队
for(int j = head[from]; j != -1; j = e[j].next)//所有边
{
int to = e[j].to;//边的终点
if(e[j].w >= max_i && color[to] == 0)//如果怒气值比max_i大且还没被染色
{
if(color[from] == 1)//起点的颜色为1
color[to] = 2;//则终点的颜色为2
else//起点的颜色为2
color[to] = 1;//则终点的颜色为1
q.push(to);//将终点入队
}
//如果终点的颜色非但不为0,还与起点的颜色相等了。就矛盾了
if(e[j].w >= max_i && color[to] == color[from])
return false;
}
}
}
return true;
}
int main()
{
cin >> n >> m;
if(n == 2)//特判只有两名罪犯的情况。在真正的比赛时,如果总是WA,那不妨考虑一下最特殊的边界情况
{
cout << 0 << endl;
return 0;
}
int l = 0,r = -inf;//二分的左右边界
memset(head,-1,sizeof(head));//初始化head数组
for(int i = 1; i <= m; ++i)//关系是相互的,所以要建立无向图
{
int a,b,c;
cin >> a >> b >> c;
add_edge(a,b,c);
add_edge(b,a,c);
r = max(r,c);
}
//sort(e + 1,e + n + 1,cmp);//按怒气值由大到小排序
//排序的话链式前向星全乱了,不能排序
while(l <= r)
{
int mid = (l + r) / 2;
if(check_bfs(mid) == true)
r = mid - 1;
else
l = mid + 1;
}
cout << r << endl;
return 0;
}


AC代码:

struct edge
{
int to;
int next;
int w;
};
edge e[200010];//因为是无向图,所以数组要开大一倍
int n,m;
int cnt = 1;
int head[20010];
bool mark[200100];
int color[20010];
bool is_bp;//是否是二分图
void add_edge(int u,int v,int w)
{
e[cnt].to = v;
e[cnt].next = head[u];
e[cnt].w = w;
head[u] = cnt;
cnt++;
}
void dfs(int p,int c)//深度优先搜索,顶点编号p,颜色c
{
color[p] = c;//染色
for(int i = head[p]; i != -1; i = e[i].next)
{
if(mark[i] == false)//如果这个下标不在二分图中

d4ab
continue;
int to = e[i].to;//终点
if(color[to] == 0)//如果没有被染色
dfs(to,-c);//染成相反数
else//否则
{
if(color[to] == c)//检验颜色是否发生冲突
is_bp = false;//不是二分图
}
}
return;
}
bool check(int max_i)
{
memset(mark,0,sizeof(mark));//重置
for(int i = 1; i <= n; ++i)
for(int j = head[i]; j != -1; j = e[j].next)
if(e[j].w >= max_i)//如果权值大于max_i
mark[j] = true;//将边的下标进行标记
memset(color,0,sizeof(color));//重置
is_bp = true;//先假设是二分图
for(int i = 1; i <= n; ++i)//防止图不连通的情况
{
if(color[i] == 0)//对没有被染色的点进行染色
{
dfs(i,1);//染色
if(is_bp == false)//如果搜索完毕后发现冲突了
return false;//该方案失败
}
}
return true;
}
int main()
{
cin >> n >> m;
if(n == 2)//特判
{
cout << 0 << endl;
return 0;
}
int l = 0,r = -inf;//二分的左右边界
memset(head,-1,sizeof(head));//初始化head数组
for(int i = 1; i <= m; ++i)//关系是相互的,所以要建立无向图
{
int a,b,c;
cin >> a >> b >> c;
add_edge(a,b,c);
add_edge(b,a,c);
r = max(r,c);
}
while(l <= r)//二分答案
{
int mid = (l + r) / 2;
if(check(mid) == true)
r = mid - 1;
else
l = mid + 1;
}
cout << r << endl;
return 0;
}


解决方法:

①加边、减边是两个不同的思考方向。

②俗话说,正难则反。

③对于本题来说,最初想到利用贪心的思想,建图后对权值进行排序,再减去权值大的边。这样也算是进行分析了,可是不对,因为关系具有传递性,A和B在一起,B和C在一起,那么A和C就一定在一起,不能通过简单的减边来做。

④题目要求将一群犯人分配到两所监狱中,在根据图论的知识建图时,我们很容易想到二分图。不在同一监狱中的犯人之间有边,在同一监狱的犯人之间没有边。这样才可以将图中的顶点分为两个不相交的集合

⑤题目问的是最大值最小化,所以考虑用二分来做,需要写一个判定二分图的check函数,二分图判定有DFS和BFS两种写法,依据的都是染色法。实现起来不难

⑥注意:int j = head[from]。j是边在边集数组中的下标,from是顶点编号。不要混了

用二分答案和二分图判定的方法稍微难写且运行时间慢。本题还有一种并查集的写法,找机会补上
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: