HIT Winter Day1 Contest
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\)
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\n", 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} - x\sum{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\n", 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\n", 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\n", 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<{\rm 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\n", max(get_weekness(l, 1), get_weekness(l, -1)));
return 0;
}
G
Description
一條長\(L\)的河上, 除了\({\rm START}\) 和 \({\rm 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\n", 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\)。最簡潔的判斷方法是,用類似借教室的字首和技巧。
程式碼中用\({\rm add}\)表示增減標記,\({\rm 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\n", 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\n", ans1, ans2);
}
return 0;
}
說點什麼
最後還是脫不了俗套,把演算法競賽拾起來了……
Day1手還是比較生,寫什麼錯什麼,也有一些東西還沒想清楚就急急忙忙寫,H題沒肝出來整個比賽節奏就暴死了,I題很友好但沒來得及碰。
眼睜睜地看著自己從rank2掉到rank5,太窩囊了x
lyd大佬tql,看一個切一個,穩得不行。
接下來的六天繼續加油(只是題解大概不會這麼詳細了x