1. 程式人生 > 實用技巧 >第一章:以太坊的學習

第一章:以太坊的學習

本題看似非常像網路流,但實際上我們非常難限制只使用兩種調料,這時候我們要敢於放棄去選擇另一條路。

從部分分出發,看到資料範圍當中出現了 \(m = n - 1\),剛好大小為 \(n\) 的樹的邊數不正好是 \(n - 1\) 嗎?恰巧的是,每種菜只能選擇不超過兩種的調料,而菜有 \(n - 1\) 道,調料有 \(n\) 種,對於沒道菜,在它所選的兩個調料中間連一條邊,既然題目剛好給了我們 \(m = n - 1\) 那麼我們可以直接考慮一下最後連出來的圖是一顆樹的情況。

不難發現這一棵決策樹的所有葉子節點的權值 \(d_i < k\),並且對於每個點它對周圍邊的貢獻都是確定的(從葉子節點開始確定)所以對於任意一個點 \(u\)

去除掉它子樹內的所有點並且減去這個點對其兒子邊的貢獻剩下的部分其實是另一個 \(u\) 為葉子節點的子問題。因此我們有了一個思路,能否每次確定葉子節點後將這些葉子節點刪掉不斷遞迴繼續確定整棵樹呢?實際上是可以的,不難發現問題在於我們怎樣找到可供選擇的葉子節點,繼而可以發現這樣一個事實,不論在哪個時刻,對於當前剩餘的所有點中的最小值 \(d\) 一定是小於 \(k\) 的,也就是它能作為葉子節點。因為假如 \(d \ge k\),那麼就會有 \(\sum d_i \ge n \times k > (n - 1) \times k\)。選定了這個葉子節點以後,我們還要選擇其在決策點上的父親才能不斷遞迴下去,不難發現如果找到另一個 \(D, D + d \ge k\)
就可以讓 \(D\) 作為 \(d\) 的父親,於是我們又可以發現剩餘 \(d_i\) 中的最大值 \(D\) 是滿足 \(D + d \ge k\) 的,否則 \(\sum d_i < \lceil \frac{n}{2} \rceil k \le (n - 1)k\),那麼我們將 \(d\) 在決策樹上刪去,將 \(D = D - (k - d)\) 重新放入當前新點就能遞迴下去了。因為每次我們都能找到合適的葉子節點和其父親,因此在這種情況下是一定有解的。分析一下這個過程的複雜度,一共遞迴 \(n - 1\) 次,每次需要找到當前的最大值和最小值,可以使用 \(\rm std :\ : multiset\)
來實現,複雜度 \(O(n \log n)\)

繼續觀察下一檔部分分 \(m \ge n\),回想剛剛 \(m = n - 1\) 是如何解決問題的,主要是在於任意時刻都會存在 \(d < k, D + d \ge k\)。繼而在 \(m \ge n\) 時我們可以發現,在任意時刻都存在 \(D \ge k\),否則 \(\sum d_i < n \times k \le m \times k\),因為可以只使用一種調料,我們直接一直找到 \(d_i \ge k\) 的調料使用一次即可,於是最後能劃歸成 \(m = n - 1\) 的子問題,複雜度 \(O((n + m) \log n)\)

最後我們來思考 \(m = n - 2\) 的情況,直覺告訴我們如果有解那麼一定存在一種解是左邊一顆樹右邊一顆樹。假如存在一種解不是上面那種形式,那麼我們依然按照上面的方式進行連邊,每個聯通塊會是 \(m \ge n - 1\) 的一個子問題,那麼對於 \(m \ge n\) 的連通塊,其上面連成自環的數量 \(S = n - 2 -\) 聯通塊數量,我們完全可以使用這些自環在不同聯通塊之間連邊,這樣恰好最後會生成 \(2\) 棵分開的樹。於是我們現在的目的在於找出一個集合 \(S\),使得 \(\sum\limits_{i \in S} d_i = (|S| - 1)k\)。可以考慮使用一個 \(dp\),令 \(dp_{i, j, k}\) 表示當前是否能選到第 \(i\) 個數,選出了 \(j\) 個數,選出來的數的和為 \(k\),但這樣的複雜度是 \(O(n ^ 3k)\) 的,實際上最後的合法狀態是關乎兩個維度的,一般的方法是把合法狀態壓到一維,觀察上面的合法判定條件:\(\sum\limits_{i \in S} d_i = (|S| - 1)k\) ,將右邊設定為定值即 \(|S|k - \sum\limits_{i \in S} d_i = k\),那麼我們可以重新定義狀態令 \(dp_{i, j}\) 表示當前選到第 \(i\) 個數,\(j = |S|k - \sum\limits_{i \in S} d_i\) 是否可行。再使用 \(\rm bitset\) 優化即可做到 \(O(\frac{n ^ 2k}{w})\),最後逐步確定集合內的數兩邊跑一遍 \(n - 1\) 的子任務即可。

#include<bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define Next(i, u) for(int i = h[u]; i; i = e[i].next)
const int N = 500 + 5;
const int M = 5000 + 5;
struct node{
    int p, w;
    bool operator < (const node &x) const{
        return w < x.w;
    }
}a[N];
bool F, book[N];
int T, n, m, k, cnt, d[N], ans[M][4];
bitset <2 * N * M> dp[N];
multiset <node> S;
multiset <node> :: iterator it;
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
void solve(int n){
    S.clear(); rep(i, 1, n) S.insert(a[i]);
    while(S.size() > 1){
        it = S.begin(); int Mi = (*it).w, Mip = (*it).p; S.erase(it);
        it = (--S.end()); int Ma = (*it).w, Map = (*it).p; S.erase(it);
        ans[++cnt][0] = Map, ans[cnt][1] = k - Mi, ans[cnt][2] = Mip, ans[cnt][3] = Mi;
        S.insert((node){Map, Ma - k + Mi});
    }
}
void solve1(){
    int x = m;
    while(x > n - 1){
        rep(i, 1, n) if(d[i] >= k){ ans[++cnt][0] = i, ans[cnt][1] = k, d[i] -= k; break;}
        --x;
    }
    rep(i, 1, n) a[i].p = i, a[i].w = d[i];
    solve(n);
}
void dfs(int i, int j){
    if(!i) return;
    if(dp[i - 1][j + m * k]) dfs(i - 1, j);
    else book[i] = true, dfs(i - 1, j - (k - d[i]));
}
void solve2(){
    dp[0].reset(), dp[0][m * k] = 1;
    rep(i, 1, n){
        if(k - d[i] >= 0) dp[i] = dp[i - 1] | (dp[i - 1] << (k - d[i]));
        else dp[i] = dp[i - 1] | (dp[i - 1] >> (d[i] - k));
    }
    if(!dp[n][k + m * k]) F = 1;
    else{
        dfs(n, k); 
        int cnt = 0;
        rep(i, 1, n) if(book[i]) a[++cnt].w = d[i], a[cnt].p = i;
        solve(cnt);
        cnt = 0;
        rep(i, 1, n) if(!book[i]) a[++cnt].w = d[i], a[cnt].p = i;
        solve(cnt);
    }
}
int main(){
    T = read();
    while(T--){
        n = read(), m = read(), k = read();
        F = cnt = 0, memset(ans, 0, sizeof(ans)), memset(book, 0, sizeof(book));
        rep(i, 1, n) d[i] = read(); 
        if(n == 1){ rep(i, 1, m) printf("%d %d\n", 1, k); continue;}
        if(m >= n - 1) solve1();
        else solve2();
        if(F) puts("-1");
        else{
            rep(i, 1, m){
                if(ans[i][3] > 0) printf("%d %d %d %d\n", ans[i][0], ans[i][1], ans[i][2], ans[i][3]);
                else printf("%d %d\n", ans[i][0], ans[i][1]);
            }
        }
    }
    return 0;
}