1. 程式人生 > >HIT Winter Day1 Contest

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\)

大連續和問題,差點一個主席樹就寫上去了(菜狗.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\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