【2018 ccpc-final Gym 102055】B.Balance of the Force(枚举最大值)

https://codeforces.com/gym/102055/problem/B

题意

宇宙中有两个阵营,分别为光明和黑暗,现在有n个骑士,每个骑士都能选择加入黑暗或者光明的阵营,加入后的能力值分别为D和L,已知有m对骑士不愿意在同一个阵营,请问如何分配,能使得能力最高的骑士和能力最低的骑士之间的能力差值最小?

题解

对于差值尽量小的问题,可以采用枚举最大值,然后使得最小值尽量大。

此题关键点便是枚举最大值,找到最大的最小值,更新答案。

首先对骑士进行二分图判定,并把他们看成一个连通块,可以知道这个连通块里的最大值mx和最小值mn有两种方案(一部分在光明,一部分在黑暗,也可以反过来),记录下这两种方案。
将这些方案按照最大值从小到大排序。
从左到右依次枚举最大值 curmx
假设当前位置最大值为curmx,位置为i
如果[1,i-1]中的连通块个数等于二分图缩点判定后的个数,那么就可以找mn的最小值curmn。更新下答案 ans = min(ans, curmx-curmn)
但是有个地方要注意,可能得出的curmn是当前方案i的孪生方案中的mn(每个块会有两种方案),因为每个连通块只能选一个方案,所以必须把前面这个孪生方案的mx的贡献给去掉。这样求出来的curmn就是正确的。
枚举完这个i后,应该更新当前位置的mn,同时如果前面有孪生方案j的话,那么两个位置都需要更新一下,更新的值便是max(a[j].mn, a[i].mn)。因为要让最小值尽量大。
上述的步骤可以用数据结构在logn时间内完成。
总复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

代码

#include 
using namespace std;
#define FOR0(a,b) for(int i = a; i < b; ++i)
#define FORE(a,b) for(int i = a; i <= b; ++i)

#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 2e5+5;
const int INF = 0x3f3f3f3f;
vector<int> G[maxn];
int color[maxn], n, m, val[maxn][2], sum[maxn];
int _mn[maxn<<3], pos[maxn];
bool vis[maxn];

struct node {
	int mx,mn;
	int id;
	bool operator < (const node& rhs) const {
		return mx < rhs.mx;
	}
}a[maxn*2];

void pushup(int rt) {
	_mn[rt] = min(_mn[rt<<1], _mn[rt<<1|1]);
}
void build(int l, int r, int rt) {
	_mn[rt] = INF;
	if(l == r) {
		return;
	}
	int mid = (l+r) >> 1;
	build(lson);
	build(rson);
	pushup(rt);
}
void update(int L, int v, int l, int r, int rt) {
	if(l == r) {
		_mn[rt] = v;
		return;
	}
	int mid = (l+r) >> 1;
	if(mid >= L) 
		update(L,v,lson);
	if(mid < L)
		update(L,v,rson);
	pushup(rt);
}
int query(int L, int R, int l, int r, int rt) {
	if(l <= L && R <= r) {
		return _mn[rt];
	} else 
		return INF;
	int mid = (l+r) >> 1;
	int res = min(query(L,R,lson),query(L,R,rson));
	return res;
}
void init() {
	for(int i = 0; i <= n; ++i) {
		G[i].clear();
		pos[i] = vis[i] = color[i] = 0;
	}
}
bool flag;
void dfs(int u, node& c1, node& c2) {
	if(!flag) return;
	c1.mx = max(c1.mx, val[u][0]);
	c1.mn = min(c1.mn, val[u][0]);
	c2.mx = max(c2.mx, val[u][1]);
	c2.mn = min(c2.mn, val[u][1]);
	for(auto v: G[u]) {
		if(color[v] == 0) {
			color[v] = 3-color[u];
			dfs(v,c2,c1);
		} else if(color[v] == color[u]) {
			flag = false;
			return;
		}
	}
}
int main() {
	int T;
	scanf("%d", &T);
	int ca = 0;
	while(T--) {
		printf("Case %d: ",++ca);
		init();
		scanf("%d%d", &n, &m);
		int u,v;
		for(int i = 0; i < m; ++i) {
			scanf("%d%d", &u, &v);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		for(int i = 1; i <= n; ++i)
			scanf("%d%d", &val[i][0], &val[i][1]);
		int sz = 1;
		flag = true;
		int cnt = 0;
		for(int i = 1; i <= n; ++i) {
			if(!color[i]) {
				cnt++;
				a[sz].mx = -INF; a[sz].mn = INF; a[sz].id = i;
				a[sz+1] = a[sz];
				color[i] = 1;
				dfs(i, a[sz], a[sz+1]);
				sz += 2;
			}
		}
		if(!flag) {
			puts("IMPOSSIBLE");
			continue;
		}
		sort(a+1,a+sz);

		// for(int i = 1; i < sz; ++i) {
		// 	cout << a[i].id <<" " << a[i].mx <<" " << a[i].mn << endl;
		// }
		for(int i = 1; i < sz; ++i) {
			sum[i] = sum[i-1];
			if(!vis[a[i].id]) {
				sum[i]++;
				vis[a[i].id]= 1;
			}
		}
		build(1,sz-1,1);
		int ans = INF;
		for(int i = 1; i < sz; ++i) {
			int curmx = a[i].mx, curmn = a[i].mn;
			if(sum[i] == cnt) {
				if(pos[a[i].id]) {
					update(pos[a[i].id], INF, 1,sz-1,1);
				}
				
				curmn = min(curmn, query(1,i-1, 1,sz-1,1));
				ans = min(ans, curmx-curmn);
			}
			if(pos[a[i].id]) {
				int ml = max(a[i].mn, a[pos[a[i].id]].mn);
				update(pos[a[i].id], ml, 1,sz-1,1);
				update(i, ml, 1,sz-1,1);
			} else {
				update(i,a[i].mn,1,sz-1,1);
				pos[a[i].id] = i;
			}
		} 
		printf("%d\n", ans);
		
	}
	return 0;
}

你可能感兴趣的:(线段树,思维,枚举,差值尽量小)