题目大意
有N个居民点在一条直线上,每个居民点有一个x表示坐标,y表示居民点的现有居民数。现在要求将居民点的居民重新分配,每个居民点的居民最远迁移的距离为R,要求分配完之后,居民点中居民数最多的居民点的居民数最少。求出居民数最多的居民点的居民数的最少值。
题目分析
求最大最小值/最小最大值的问题,可以尝试二分法,给出边界,取边界中点作为尝试值,判断尝试值是否满足要求,根据是否满足,不断调整边界,最后得到最大最小值/最小最大值。
自己做的时候,只知道具体的框架,但是没有解出来,最后参考了
居民迁移-二分+贪心 解法。
首先将所有居民点的原有居民数,以及居民点能够到达的最左和最右边界提取出来,用于后续的安置。则问题为:将各个居民点的居民,重新分配到各个安置点,在分配的时候,两个指针,一个指向可以安置的居民点的位置,一个指向当前被分配的居民点的位置。
实现
#include<stdio.h> #include<iostream> #include<string> #include<string.h> #include<vector> #include<stack> #include<queue> #include<deque> #include<list> #include<set> #include<set> #include<map> #include<functional> #include<algorithm> using namespace std; int T; int N, R; struct Seg{ int left; int right; int num; }; Seg segs[100005]; int points[100005]; bool Cmp(const Seg& seg1, const Seg seg2){ return seg1.left < seg2.left; } /* 这道题一开始,想的是在居民点现有这么些居民数的基础上进行迁移,这样想会很乱。换一种想法:有N个居民集体,每个集体中有 num[i]个居民,且每个 集体中的人的活动范围为 [left[i], right[i]],将这N个集体安放到N个点上。 */ bool canSet(int max_people){ //cur_seg_index 表示当前待迁移的居民点的序号, cur_pos 表示当前的目的点的序号 int cur_seg_index = 0, cur_pos = 0; int cur_people = segs[0].num, cur_volume = max_people; //将各个居民集体,迁移到各个居民点中去 //cur_people 表示当前要迁移的居民点中还剩余待迁移的居民的个数, cur_volume 表示当前要迁移到的目的地的空余位置 while (true){ if (cur_people <= cur_volume){//可以将当前点待迁移的所有居民都放置到 目的点 cur_seg_index++; if (cur_seg_index == N) //迁移完了所有带迁移的居民点,则成功 return true; cur_volume -= cur_people; //当前目的点可以放置的居民数目减少 cur_people = segs[cur_seg_index].num; //当前待迁移点的居民数 //cur_seg_index 增加,可能导致目的点 在当前待迁移居民点的左侧 过远的位置,从而需要向右调整目的点 if (points[cur_pos] < segs[cur_seg_index].left){ while (points[cur_pos] < segs[cur_seg_index].left) cur_pos++; cur_volume = max_people; //同时更新 目的点可以放置的最大人数 } } else{ //需要向下一个目的点放置剩余的居民 cur_pos++; if (cur_pos == N) //没有可以放置的目的点了,返回失败 return false; //当前待迁移的居民点无法再向 目的点进行迁移,返回失败 if (points[cur_pos] > segs[cur_seg_index].right) return false; //当前待迁移居民点 居民数减少 cur_people -= cur_volume; //当前目的点可以存放居民数 为max_people cur_volume = max_people; } } return true; } int main(){ scanf("%d", &T); while (T--){ scanf("%d %d", &N, &R); int end = 0; for (int i = 0; i < N; i++){ scanf("%d %d", &points[i], &segs[i].num); segs[i].left = points[i] - R; segs[i].right = points[i] + R; end = max(end, segs[i].num); } sort(points, points + N); sort(segs, segs + N, Cmp); int beg = 0; end++; while (beg < end){ int mid = (beg + end) / 2; if (canSet(mid)){ end = mid; } else beg = mid + 1; } printf("%d ", beg); } return 0; }
#include<iostream> #include<stdio.h> #include<algorithm> #include<string.h> using namespace std; #define N 100005 struct Point{ int x; int y; }; Point points[N]; //left:居民点的居民能够到达的最左边的位置, right居民点的居民能够到达的最右边的位置 //num:居民点的原有的居民数目 struct Seg{ int left, right, num; }; Seg segs[N]; bool cmp(Point a, Point b){ return a.x < b.x; } /*判断是否存在分配方案,使得每个居民点的居民数最大为middle 分配之前,将每个居民点(x, y) 转换为 (left, right, num),然后对(left, right, num)数组进行分配。 将(left, right, num) 将num个居民根据其所能到达的最远位置left,right ,分配到其所能到达的居民点去。 pos 表示当前需要对哪个seg(left, right, num)进行分配; num表示当前需要分配的(left, right, num)居民点的现有居民数。 从左到右,遍历所有的居民点 points,进行安置 segs. */ bool canSet(int n, int R, int middle){ int pos = 0, num = segs[0].num; //pos 为当前需要分配的seg for (int i = 0; i < n; i++){ //当前可以分配居民的安置点 int p = points[i].x; int volume = middle; //如果当前的安置点p大于pos所能到达的最远边界,说明pos在之前没有被分配完,则此次分配失败 if (segs[pos].right < p) return false; int j; //尝试将各个seg的居民,分配到 当前可以分配的安置点p for (j = pos; j <= n; j++){ if (j == n) //都分配完 return true; //要分配的居民点已经无法到达安置点p,则分配到下一个安置点 if (segs[j].left > p){ pos = j; num = segs[j].num; break; } int cnt; if (j == pos) cnt = num; else cnt = segs[j].num; volume -= cnt; if (volume < 0){//安置点无法容纳更多的居民,则分配到下一个安置点 pos = j; num = -volume; break; } } } return false; } int main(){ int T, n, R, min, max; scanf("%d", &T); while (T--){ scanf("%d %d", &n, &R); min = 1 << 30, max = 0; for (int i = 0; i < n; i++){ scanf("%d %d", &points[i].x, &points[i].y); min = min < points[i].y ? min : points[i].y; max = max > points[i].y ? max : points[i].y; } //将居民点按照位置从小到大排序 sort(points, points + n, cmp); //根据原有的居民点的居民数,以及居民所最远迁移的距离,初始化segs数组,得到每个居民点能够到达的最远边界,以及原有的居民数目 for (int i = 0; i < n; i++){ segs[i].left = points[i].x - R; segs[i].right = points[i].x + R; segs[i].num = points[i].y; } while (min < max){ int mid = (min + max) / 2; //判断是否存在分配方案,使得每个居民点最多有mid个居民 if (canSet(n, R, mid)){ max = mid; } else min = mid + 1; } printf("%d ", max); } return 0; }