1. 程式人生 > 其它 >[考試總結]ZROI-21-NOIP衝刺-TEST6 總結

[考試總結]ZROI-21-NOIP衝刺-TEST6 總結

ZROI-21-NOIP衝刺-TEST6 總結

莫要問 TEST5 去哪了,他有 4 道巨大惡心的計數題,補不動了QwQ

#T1 夏令營

#題意簡述

給定 \(n(n\leq3\times10^5)\) 個區間,每個區間有價值 \(h_i(1\leq3\times10^5)\),每個點 \(i(i\in[1,D])\) 只能選最多 \(k\) 個覆蓋該點的區間,問可到的價值和最大的點的最大價值和。

#大體思路

注意到我們只關注單點,且對於覆蓋資訊相同的點並沒有實際區別,於是我們可以考慮將原本的一個區間拆成兩個操作:加入和刪除。

現在考慮如何快速維護這兩個操作。注意到覆蓋每個點的的區間分為兩部分:包含在該點的答案裡的和候選的,如果我們按照時間順序維護這兩個操作,不難發現,當新加入一個區間時,如果這個區間的價值大於當前已選區間的最小者,那麼就把它加入答案,將最小者加入候選部分,否則直接加入候選;刪除時,如果在候選部分就直接刪,否則需要取出候選部分的最大值加入答案。

不難發現兩個 priority_queue 就可以完成上面所需的操作(對頂堆?),刪除時直接維護資訊進行懶刪除即可,每次修改只會進行 \(O(1)\) 級別的操作,於是單次修改的時間複雜度為 \(O(\log n)\),總體時間複雜度為 \(O(n\log n)\).

#Code

#define ll long long
#define pii pair <int, int>
#define plli pair <long long, int>
#define mp(a, b) make_pair(a, b)
#define mset(l, x) memset(l, x, sizeof(l))

const int N = 500010;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

template <typename T> inline T Max(T a, T b) {return a > b ? a : b;}

struct Activity {
    int id, l, r; ll x;
    inline bool operator < (const Activity &b) const {return x > b.x;}
} a[N];

int T, n, d, k, q[N], siz, frt, tal, vis[N], st[N]; ll ans, sum;

priority_queue <plli > pq, cand;

vector <pii > qry[N];

inline bool cmp(Activity x, Activity y) {return x.l == y.l ? x.r < y.r : x.l < y.l;}

inline void reset() {
    frt = 0, tal = -1; ans = 0; mset(vis, 0);
    mset(st, 0); while (pq.size()) pq.pop();
    while (cand.size()) cand.pop(); siz = sum = 0;
    for (int i = 1; i <= d; ++ i) qry[i].clear();
}

void insert(int x) {
    if (siz < k) {
        pq.push(mp(-a[x].x, x)); ++ siz;
        sum += a[x].x; st[x] = 1;
    } else {
        plli tmp = pq.top();
        while (pq.size() && vis[tmp.second]) {pq.pop(); tmp = pq.top();}
        if (-tmp.first < a[x].x) {
            pq.pop(); cand.push(mp(-tmp.first, tmp.second));
            sum += a[x].x + tmp.first;
            pq.push(mp(-a[x].x, x));
            st[tmp.second] = 0, st[x] = 1;
        } else cand.push(mp(a[x].x, x)), st[x] = 0;
    }
}

void del(int x) {
    if (st[x] == 1) -- siz, sum -= a[x].x;
    vis[x] = 1, st[x] = 0;
    while (cand.size() && siz < k) {
        plli tmp = cand.top(); cand.pop();
        if (vis[tmp.second]) continue;
        ++ siz, sum += tmp.first; st[tmp.second] = 1;
        pq.push(mp(-tmp.first, tmp.second));
    }
}

int main() {
    read(T);
    for (int t = 1; t <= T; ++ t) {
        read(d); read(n); read(k); reset();
        for (int i = 1; i <= n; ++ i)
          read(a[i].x), read(a[i].l), read(a[i].r);
        sort(a + 1, a + n + 1, cmp);
        for (int i = 1; i <= n; ++ i) a[i].id = i;
        for (int i = 1; i <= n; ++ i) {
            qry[a[i].l].push_back(mp(1, i));
            qry[a[i].r + 1].push_back(mp(-1, i));
        }
        for (int i = 1; i <= d; ++ i) {
            for (auto k : qry[i])
              if (k.first == -1) del(k.second);
              else insert(k.second);
            ans = Max(sum, ans);
        }
        printf("Case #%d: %lld\n", t, ans);
    }
    return 0;
}

#T2 遊戲

#題意簡述

給定有一個 \(2n(n\leq5\cdot10^5)\) 個點的環,同時給定 \(n\) 條額外的邊(沒有重複的端點),要求將整張圖的所有點染為兩種顏色,滿足沒有額外邊兩端顏色相同,在環上沒有連續的三個人顏色相同。給出構造方案,若無可行的構造方案,輸出 impossible

#大體思路

先來你考慮環上的約束條件,我們只需要保證 1 與 2 不同,3 與 4 不同……\(2n-1\)\(2n\) 不同即可,於是我們只把 \(2i\)\(2i-1(1\leq i\leq n)\) 相連即可,再考慮額外的 \(n\) 條邊,發現每次最多也只會加入 \(2\)

個點,最終形成一個具有偶數個點的環,這樣的圖一定是一個二分圖,所以直接黑白染色即可。時間複雜度 \(O(n)\).

#Code

const int N = 2000010;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

struct Edge {int u, v, nxt;} e[N];

int n, head[N], ecnt(1), vis[N], col[N];

inline void add_edge(int u, int v) {
    e[ecnt].u = u, e[ecnt].v = v;
    e[ecnt].nxt = head[u], head[u] = ecnt ++;
}

void mark(int x, int c) {
    col[x] = c, vis[x] = 1;
    for (int i = head[x]; i; i = e[i].nxt)
      if (!vis[e[i].v]) mark(e[i].v, c ^ 1);
}

int main() {
    read(n);
    for (int i = 1; i <= n; ++ i) {
        int u, v; read(u), read(v);
        add_edge(u, v); add_edge(v, u);
    }
    for (int i = 1; i <= n; ++ i) {
        add_edge((i << 1) - 1, i << 1);
        add_edge(i << 1, (i << 1) - 1);
    }
    for (int i = 1; i <= n << 1; ++ i)
      if (!vis[i]) mark(i, 0);
    for (int i = 1; i <= n << 1; ++ i)
      if (col[i]) putchar('X'); else putchar('Y');
    return 0;
}

#T3 字串

#題意簡述

給定 \(n(n\leq100)\) 個字元在字串 \(S\) 中的出現次數 \(a_i(a_i\leq10^9)\),要求將這些字元用 0/1 進行編碼,每個字元編碼的長度不超過 \(l(l\leq30)\),問 \(S\) 在編碼後的最小長度。

#大體思路

因為編碼長度有限制,所以直接用哈夫曼樹是不行的(實際上因為資料過水可以過 QnQ),但是可以借鑑哈夫曼樹的思想,使用二叉樹進行編碼。

\(f_{i,j,k}\) 為當前處理了出現次數前 \(i\) 大的字元,在二叉樹的第 \(j\) 層,還剩 \(k\) 個結點可用時的最小長度和,容易發現具有轉移:

\[\begin{aligned} f_{i,j,k}&\to f_{i+1,j,k-1},\\ f_{i,j,k}&\to f_{i,j+1,k\times2}. \end{aligned} \]

然後直接轉移即可,時間複雜度為 \(O(n^2l)\).

#Code

#define ll long long
#define mset(l, x) memset(l, x, sizeof(l))

const int N = 110;
const ll INF = 1e18;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

template <typename T> inline T Min(T a, T b) {return a < b ? a : b;}

ll n, l, a[N], ans, f[N][40][N];

int main() {
    read(n), read(l);
    while (n || l) {
        for (int i = 1; i <= n; ++ i) read(a[i]);
        auto cmp = [](int x, int y){return x > y;};
        sort(a + 1, a + n + 1, cmp); mset(f, 0x3f);
        f[0][1][2] = f[0][1][1] = 0; ans = INF;
        for (int i = 0; i < n; ++ i)
          for (int j = 1; j <= l; ++ j)
            for (int k = 1; k <= Min(n, 1ll << j); ++ k)
              if (f[i][j][k] < INF) {
                  f[i + 1][j][k - 1] = Min(f[i + 1][j][k - 1], f[i][j][k] + 1ll * a[i + 1] * j);
                  f[i][j + 1][k * 2] = Min(f[i][j + 1][k * 2], f[i][j][k]); 
              }
        for (int i = 0; i <= l; ++ i)
          for (int j = 0; j <= Min(n, 1ll << i); ++ j)
            ans = Min(ans, f[n][i][j]);
        printf("%lld\n", ans); read(n), read(l);
    }
    return 0;
}

#T4 金色飛賊

巨大惡心的計算幾何,暫且咕著。