HIT Winter 20190112 - 二分 三分 贪心
[比赛链接][https://vjudge.net/contest/278692]
A
Description
判断能否经过(s)步从((0,0))走到((a,b)),每次只能走到上下左右相邻的格子。
Solution
注意(a),(b)可能是负数。判断(s)与(|a| + |b|)之差的奇偶性即可。
不要用库文件里的abs函数,手写保险。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
int a, b, s;
int get_abs(int x) { return x >= 0 ? x : -x; }
int main()
{
scanf("%d%d%d", &a, &b, &s);
int step = get_abs(a) + get_abs(b);
printf(((s - step) % 2 == 1 || s < step) ? "No" : "Yes");
return 0;
}
B
Description
对于一个长度为(n)的序列有(C(n, 2))个(|a_i - a_j|)。将这些差值排序,问最中间的(第(C(n, 2) / 2)个)差值是多少。
Solution
将(a_i)排序后二分答案(x),通过计算(x)在差值中的“排名”,移动二分的上下界。对于每个(a_i),用lower_bound找出有多少在(a_i)和(a_i+x)之间的数,这个个数记为(c_i)。将所有(c_i)相加,得到序列中差值小于等于(x)的数对个数,也就是(x)这个差值的“排名”。
考场上一度这个问题转化成了第(k)大连续和问题,差点一个主席树就写上去了(菜狗.jpg
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
typedef long long LL;
const int N = 100000 + 5, maxX = 1000000000;
int n, k;
int a[N];
bool judge(int x) {
int sum = 0;
rep(i, 1, n) {
int val = a[i] + x;
int range = lower_bound(a + 1, a + n + 1, val) - (a + i + 1);
sum += range;
}
return sum < k;
}
int main()
{
while (scanf("%d", &n) == 1) {
k = (LL)n * (n - 1) / 2;
k = (k % 2 == 1) ? k / 2 + 1 : k / 2;
rep(i, 1, n) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
int l = 0, r = maxX + 1;
while (l + 1 < r) {
int mid = l + (r - l) / 2;
if (judge(mid)) l = mid; else r = mid;
}
printf("%d
", l);
}
return 0;
}
C
Description
有(n)场考试,给出每场答对的题数(a)和这场一共有几道题(b),求去掉(k)场考试后,求加权平局值的最大值。
Solution
(如果工大也来一次这个活动该多好(幻想
裸的01分数规划。
二分答案(x)。我们希望选出(n-k)门课(设选出的集合为(S)),使得(sum{a_j}/sum{b_j} ge x),其中(j in S)
移项,有(sum{a_j} - xsum{b_j} ge 0)
设(f(j) = a_j - xb_j),则(sum{f(j)} ge 0)
这样就可以贪心了,选出(f(j))最大的(n-k)门课,计算(f)的和,判断其是否非负即可。
注意"%.0f"这种占位符输出的就是四舍五入的结果,不用+0.5。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define dep(i, a, b) for (int i = a; i >= b; i--)
const int N = 1000 + 5;
const double eps = 1e-5;
double sum1, sum2;
double f[N];
int n, k;
int a[N], b[N];
double dabs(double x) { return x > 0 ? x : -x; }
double calc_sum(double x) {
rep(i, 1, n) f[i] = (double)a[i] - (double)b[i] * x;
sort(f + 1, f + n + 1);
double sum = 0;
dep(i, n, k + 1) sum += f[i];
return sum;
}
int main()
{
while (scanf("%d%d", &n, &k) == 2) {
if (n == 0 && k == 0) break;
rep(i, 1, n) scanf("%d", &a[i]);
rep(i, 1, n) scanf("%d", &b[i]);
double l = 0, r = 1;
while (r - l > eps) {
double mid = l + (r - l) / 2;
double sum = calc_sum(mid);
if (sum < 0) r = mid; else l = mid;
}
printf("%.0f
", l * 100);
}
return 0;
}
D
Description
有(n)滩泥,需要用木板覆盖。给出每摊泥的起始位置和木板长度(l),求最少需要多少木板才能覆盖这些泥。
Solution
简单贪心:“尽量往右”思想。
代码中用一个变量beg表示当前这摊泥需要从哪里开始覆盖。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define mp make_pair
const int N = 10000 + 5;
int n, l, x, y;
pair<int, int> mud[N];
int main()
{
scanf("%d%d", &n, &l);
rep(i, 1, n) {
scanf("%d%d", &x, &y);
mud[i] = mp(x, y);
}
sort(mud + 1, mud + n + 1);
int beg = mud[1].first;
int ans = 0;
rep(i, 1, n) {
int cur = (mud[i].second - beg) / l;
if (cur * l < mud[i].second - beg) cur++;
ans += cur;
if (i < n)
beg = max(mud[i + 1].first, beg + l * cur);
}
printf("%d
", ans);
return 0;
}
E
Description
有(n)个人要渡河,但是只有一艘船,船上每次最多只能载两个人,渡河的速度由两个人中较慢的那个决定,小船来回载人直到所有人都渡河,求最短的渡河时间。
Solution
把(n)个人按渡河时间从小到大排序。设(f(i))表示前(i)个人完成渡河(全部到达右岸)所需的最少时间。
显然,最快的人和次快的人应该先划到右岸。考虑第(i (i>=3))个人渡河(从左岸到右岸),有两种策略:
- 最快的人把船从右岸划回左岸,带着(i)划回右岸。
- 后退一步,假设(i-1)还在左岸。最快的人留在右岸,次快的人把船从右岸划回左岸,(i)和(i-1)一起划船到右岸。然后最快的人划回左岸,带着次快的人划到右岸。
取较优者即可。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
const int N = 1000 + 5;
int T, n;
int a[N], f[N];
int main()
{
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
rep(i, 1, n) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
f[1] = a[1];
f[2] = a[2];
rep(i, 3, n)
f[i] = min(f[i - 1] + a[1] + a[i], f[i - 2] + a[2] + a[i] + a[1] + a[2]);
printf("%d
", f[n]);
}
return 0;
}
F
Description
给定一个序列A。一个区间的poorness定义为该区间元素和的绝对值。序列A的weakness等于所有区间最大的poorness。求一个(x)使得,序列A全部减(x)后weakness最小。(1 le n le 200000)
Solution
直觉上weekness是关于(x)的凹函数。直觉是对的。三分答案即可。
注意这里二分的结束条件没有用(r-l<{ m eps}),而是是相邻两次计算得到的weekness小于eps。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
const int maxA = 10000, N = 200000 + 5;
const double eps = 1e-8;
int n;
int a[N];
double d[N];
double w1, w2;
double dabs(double x) { return x > 0 ? x : -x; }
double get_weekness(double x, double sgn) {
double res = 0;
d[0] = 0;
rep(i, 1, n) {
d[i] = max(d[i - 1], 0.0) + (a[i] - x) * sgn;
res = max(res, d[i]);
}
return res;
}
int main()
{
scanf("%d", &n);
rep(i, 1, n) scanf("%d", &a[i]);
double l = -(maxA + 1), r = maxA + 1;
do {
double mid1 = l + (r - l) / 2;
double mid2 = mid1 + (r - mid1) / 2;
w1 = max(get_weekness(mid1, 1), get_weekness(mid1, -1));
w2 = max(get_weekness(mid2, 1), get_weekness(mid2, -1));
if (w1 < w2) r = mid2; else l = mid1;
} while (dabs(w1 - w2) > eps);
printf("%.9f
", max(get_weekness(l, 1), get_weekness(l, -1)));
return 0;
}
G
Description
一条长(L)的河上, 除了({ m START}) 和 ({ m END}) 还有(N)个石子,分别距离起点距离(d_i),求去掉(M)个石子后相邻的最小距离的最大值。
Solution
NOIP原题。
二分答案(x)。问题转化成,判断:能否去掉(M)个石子,使得任意两个相邻石子间距离都不小于(x)。这个简单循环判断即可。注意可能连续去掉多个石子,也别忘判断起点到第(1)个石子、第(n)个石子到终点是否满足条件。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
const int N = 500000 + 5;
int L, n, m;
int a[N], t[N];
int main()
{
scanf("%d%d%d", &L, &n, &m);
rep(i, 1, n) scanf("%d", &a[i]);
a[0] = 0; a[n + 1] = L;
sort(a + 1, a + n + 1);
int l = 0, r = L + 1;
while (l + 1 < r) {
int mid = l + (r - l) / 2;
bool flag = true;
int rest = m;
rep(i, 0, n + 1) t[i] = a[i];
rep(i, 1, n + 1)
if (t[i] - t[i - 1] < mid) {
rest--;
if (rest < 0) {
flag = false;
break;
}
t[i] = t[i - 1];
}
if (flag) l = mid; else r = mid;
}
printf("%d
", l);
return 0;
}
H
Description
有(n)朵花,每朵花都有一定的高度(a_i),(m)天之后要把这些花送给别人。这(m)天里可以通过淋花来让花长得尽可能高。每天只能淋一次,一次覆盖的范围是连续的(w)朵,淋完水后被淋的花会在当天长高(1)个单位。要求经过(m)天后,最大化最矮的花的高度。
Solution
此题令我身败名裂。二分之后的判断一直思路混乱。
二分答案(x),设(len_i = x - a_i)。问题变成,判断:能否将一个长度为(n)的全0序列,经过(m)次(w)区间加1操作后,第(i)个位置上的元素不小于(len_i)。
是不是很像某年NOIP的堆积木?这里只多了一个限制条件,即操作区间的长度(w)。最简洁的判断方法是,用类似借教室的前缀和技巧。
代码中用({ m add})表示增减标记,({ m val})表示前缀和,即当前元素被覆盖的次数。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define mp make_pair ;
typedef long long LL;
const int N = 100000 + 5;
const LL maxA = 1000000000;
typedef pair<int, int> Pii;
int n, m, w, head, tail;
int a[N], len[N], add[N], val[N];
Pii q[N];
bool judge(int x) {
rep(i, 1, n) len[i] = max(0, x - a[i]);
LL sum = 0;
memset(add, 0, sizeof(add));
memset(val, 0, sizeof(val));
rep(i, 1, n) {
val[i] = val[i - 1] + add[i];
if (val[i] < len[i]) {
add[i] = (len[i] - val[i]);
if (i + w <= n) add[i + w] = -(len[i] - val[i]);
val[i] += add[i];
}
if (add[i] > 0) sum += add[i];
}
if (sum > m) return false;
return true;
}
int main()
{
scanf("%d%d%d", &n, &m, &w);
rep(i, 1, n) scanf("%d", &a[i]);
int l = 0, r = maxA + m + 1;
while (l + 1 < r) {
int mid = l + (r - l) / 2;
if (judge(mid)) l = mid; else r = mid;
}
printf("%d
", l);
return 0;
}
I
Description
给你(N)个机器和(M)个任务, 每个任务有两个值花费时间(x)和难度(y), 每个机器也有两个值最大工作时间(x')和最大工作难度(y'), 机器可以胜任某个工作的条件是(x' ge x)且(y' ge y),机器胜任一个工作可以拿到(500x+2y)的钱,现在问你怎么匹配才能使匹配数最大,匹配数相同时要求钱数最多。
Solution
典型的双属性贪心题。
我们发现,对于同样的机器资源,完成工作时间更多的任务总是划算的。假设有任务A((x_a, y_a)),任务B((x_b, y_b)),且(x_a > x_b, y_a < y_b)。由于(x)的权重为500而(y)的权重为2(且(y)最大值仅有100),所以即便(x_b)只小1,(y_b)大100,做任务B也没有做任务A划算。
下面考虑如何匹配。对任务和机器都以工作时间为第一关键字、难度为第二关键字排序。依次考虑排序后的每个任务,我们维护一个set,set中存放工作时间满足当前任务的机器,set内部按难度排序。对于当前任务,我们贪心地匹配(y')大于等于(y)且最小的机器即可。
要注意set.end()不是尾元素,end()的前驱才是
再注意set中只剩一个元素时erase()它会RE,要用clear()。
还要注意此题钱数会爆int。
Code
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define dep(i, a, b) for (int i = a; i >= b; i--)
#define fill(a, x) memset(a, x, sizeof(x))
#define mp make_pair
#define pb push_back
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> Pii;
const int N = 100000 + 5, M = 100000 + 5;
Pii mach[N], task[M];
bool cmp1(Pii x, Pii y) { return x > y; }
bool cmp2(int x, int y) { return mach[x].second < mach[y].second; }
multiset<Pii> ready;
multiset<Pii>::iterator iter;
int n, m;
int get_max_level() {
multiset<Pii>::iterator iter = ready.end();
iter--;
return iter->first;
}
int main()
{
while (scanf("%d%d", &n, &m) == 2) {
rep(i, 1, n) scanf("%d%d", &mach[i].first, &mach[i].second);
rep(i, 1, m) scanf("%d%d", &task[i].first, &task[i].second);
sort(mach + 1, mach + n + 1, cmp1);
sort(task + 1, task + m + 1, cmp1);
int j = 1, ans1 = 0;
LL ans2 = 0;
ready.clear();
rep(i, 1, m) {
while (mach[j].first >= task[i].first && j <= n) {
ready.insert(mp(mach[j].second, mach[j].first));
j++;
}
if (ready.size() == 0 || get_max_level() < task[i].second) continue;
iter = ready.lower_bound(Pii(task[i].second, 0));
ans1++;
ans2 += task[i].first * 500LL + task[i].second * 2LL;
if (ready.size() == 1) ready.clear(); else ready.erase(iter);
}
printf("%d %lld
", ans1, ans2);
}
return 0;
}
说点什么
最后还是脱不了俗套,把算法竞赛拾起来了……
Day1手还是比较生,写什么错什么,也有一些东西还没想清楚就急急忙忙写,H题没肝出来整个比赛节奏就暴死了,I题很友好但没来得及碰。
眼睁睁地看着自己从rank2掉到rank5,太窝囊了x
lyd大佬tql,看一个切一个,稳得不行。
接下来的六天继续加油(只是题解大概不会这么详细了x