Evanyou Blog 彩带

洛谷P1525 关押罪犯 题解

洛谷题面

前言

题目大意

\(N\) 个罪犯,如果两名罪犯被关押到了一座监狱,那么就会产生一定的怨气值。现在有两座监狱,求一种分配监狱方法,使得最大的怨气值最小。

题意分析

时间紧迫者略

由题意得,怨气值有大有小,求怨气值之和最小,最先想到的就是贪心。

贪心策略最简单的莫过于,按照怨气值从大到小排序,在把怨气值大的一对罪犯分开放的前提下,其他能分开放就分开放,不能分开放就累加怨气值。

然而,贪心的策略显然有些问题。

比如 \(x\) 和 \(y\) 的怨气值为 \(5\),其他还有 \(6\) 个罪犯,都只分别和 \(x\) 和 \(y\) 之间有 \(1\) 的怨气值,且这 \(6\) 个罪犯之间没有怨气值。

按照最开始的贪心策略,\(x\) 和 \(y\) 分开放在两个监狱,其他 \(6\) 个罪犯因此不管放在哪个监狱,都会和 \(x\) 或 \(y\) 产生 \(6\) 的怨气值。最后答案会输出 \(6\)。

手玩数据发现,如果 舍小家为大家 把 \(x\) 和 \(y\) 放在一个监狱,其他 \(6\) 个罪犯都放在另一个监狱,那么最后正确答案应该是 \(5\)。

所以贪心是错的吗?

显然不是。只是题目不只这么简单。

博主太菜,下面分两种方法解该题目,分别是 并查集 和 二分图

并查集

我们不必太在乎每个罪犯具体在哪个监狱,毕竟题目没有要求,只需要知道哪些罪犯属于一个集合即可。

并查集恰恰好可以解决我们的需求。我们拿一个额外的数组 \(b[]\),来记录每一个罪犯的敌人,且是不在一个集合内的敌人。

按照贪心思想,将怨气值从大到小排序,枚举每一对怨气值,如果产生怨气值的两位罪犯 \(x\) 和 \(y\) 属于一个集合内,由于怨气值已从大到小排序且题目要求最大的怨气值,那么直接输出答案即可。

而如果 \(x\) 和 \(y\) 不属于同一个集合内,那么将 \(y\) 与 \(b[x]\) 放在一个集合内, \(x\) 与 \(b[y]\) 放在一个集合内。

随后按照上面步骤操作即可。

代码:

#include<bits/stdc++.h>
#include<cctype>
#define in(a) a = read()
#define Min(a, b) a < b ? a : b
#define Max(a, b) a > b ? a : b 
#define out(a) write(a)
#define outn(a) out(a),putchar('\n')
#define ll long long
#define rg register
#define New int
using namespace std;
inline New read()
{
    New X = 0,w = 0;
	char ch = 0;
	while(!isdigit(ch))
	{
		w |= ch == '-';
		ch=getchar();
	}
	while(isdigit(ch))
	{
		X = (X << 3) + (X << 1) + (ch ^ 48);
		ch = getchar();
	}
    return w ? -X : X;
}
inline void write(New x)
{
     if(x < 0) putchar('-'),x = -x;
     if(x > 9) write(x/10);
     putchar(x % 10 + '0');
}

const int N = 20000 + 10;
const int M = 100000 + 10;

int a[N], b[N];
struct Prison//存怨气值 
{
	int x,y;//产生怨气值的两个罪犯 
	int dan;//怨气值 
} f[MAXM];
int n, m;
int q, p, z;

inline bool cmp(Prison a,Prison n)//快排 
{
	return a.dan > n.dan;
}

inline int find(int num)//并查集 
{
	if(a[num] == num)
		return num;
	return a[num] = find(a[num]);
}

inline bool check(int x,int y)//判断是否在一个集合内 
{
	return find(x) == find(y);
}

inline void unionn(int x,int y)//合并 
{
	a[find(a[x])] = find(a[y]);
	return;
}

int main()
{
	in(n), in(m);
	for(rg int i = 1; i <= n; ++i)
		a[i] = i;
	for(rg int i = 1; i <= m; ++i)
		in(f[i].x), in(f[i].y), in(f[i].dan);
	sort(f+1, f+1+m, cmp);//快排 
	for(rg int i = 1; i <= m; ++i)
	{	
		q = f[i].x,p = f[i].y,z = f[i].dan;
		if(check(p,q))//如果在一个集合内 
		{
			outn(z);
			return 0;
		}
		//不在一个集合内 
		if(!b[q])
			b[q] = p;
		else unionn(b[q],p);
		if(!b[p])
			b[p] = q;
		else unionn(b[p],q);
	}
	puts("0");
	return 0;
}

二分图

这里二分图不是匹配,而是判定。二分图判定普遍使用染色法。

题目中求“最大值最小”,不由自主地想到 二分

将罪犯看作点,而二分图的两个集合就是监狱。

罪犯之间的怨气值,不难看出就是边的权值了。

还是先快排,由于二分,需要 从小到大排序 ,然后二分出一个怨气值 \(mid\),枚举所有边,如果这条边的权值小于等于 \(mid\),也就是不会对二分的答案造成影响,则不需要操作。否则进行染色法操作。

如果染色失败,出现矛盾,那么说明二分的答案 \(mid\) 小了,反之则大了,移动左右边界即可。

博主写完后才发现 这里有一个技巧 ,普遍 \(OIer\) 都是左边界取 \(0\),有边界取 \(inf\),然而最大怨气值不会超过排序后边集最后一条边的边权,且最终答案要么是 \(0\) (左边界),要么是其中一条边边权。

故从 二分怨气值 转换为 二分边的编号 。博主没有优化,太懒了

代码

#include<bits/stdc++.h>
#include<cctype>
#define in(a) a = read()
#define out(a) write(a)
#define outn(a) out(a),putchar('\n')
#define ll long long
#define rg register
#define New int
using namespace std;
inline New read()
{
    New X = 0,w = 0;
	char ch = 0;
	while(!isdigit(ch))
	{ 
		w |= ch == '-';
		ch=getchar();
	}
	while(isdigit(ch))
	{
		X = (X << 3) + (X << 1) + (ch ^ 48);
		ch = getchar();
	}
    return w ? -X : X;
}
inline void write(New x)
{
     if(x < 0) putchar('-'),x = -x;
     if(x > 9) write(x/10);
     putchar(x % 10 + '0');
}

const int N = 200000 + 2;

struct Node
{
    int nxt, val;
};
int n, m, l, r;
vector<Node>nei[N];//用邻接矩阵存图 

inline bool work(int mid)//染色法判定 
{
    queue <int> q; 
    int color[20009] = {0};
	for(rg int i = 1; i <= n; ++i)
	{	
		if(color[i])
			continue;
		q.push(i);
		color[i] = 1;
		while(!q.empty())
		{
			int x = q.front();
			q.pop();
			int len = nei[x].size();
			for(rg int j = 0; j < len; ++j)//枚举邻居 
			{
				int nxt = nei[x][j].nxt;
				int val = nei[x][j].val;
				if(val < mid)//边权不影响答案 
					continue;
				//边权影响答案 
				if(color[nxt] == color[x])//染色法矛盾 
					return false;
				if(color[nxt] == 0)//如果该点未被染色 
					q.push(nxt);//加入队列 
				color[nxt] = 3 - color[x];//这里很有技巧,感性理解 
			}
		}
	}
    return true;
}

int main()
{
	in(n), in(m);
	for(rg int i = 1; i <= m; ++i)
	{
		int x = read(), y = read(), z = read();
		r = max(r, z);
		nei[x].push_back((Node){y, z});//存边 
		nei[y].push_back((Node){x, z});
	}
	++r;
    while(l+1 < r)
    {
    	int mid = (l+r) >> 1;
		if(work(mid))
			r = mid;
        else l = mid;
    }
    outn(l);
    return 0;
}
posted @ 2020-03-17 20:24  御·Dragon  阅读(375)  评论(0)    收藏  举报



Contact with me