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  阅读(301)  评论(0编辑  收藏  举报



Contact with me