第一章:以太坊的學習
本題看似非常像網路流,但實際上我們非常難限制只使用兩種調料,這時候我們要敢於放棄去選擇另一條路。
從部分分出發,看到資料範圍當中出現了 \(m = n - 1\),剛好大小為 \(n\) 的樹的邊數不正好是 \(n - 1\) 嗎?恰巧的是,每種菜只能選擇不超過兩種的調料,而菜有 \(n - 1\) 道,調料有 \(n\) 種,對於沒道菜,在它所選的兩個調料中間連一條邊,既然題目剛好給了我們 \(m = n - 1\) 那麼我們可以直接考慮一下最後連出來的圖是一顆樹的情況。
不難發現這一棵決策樹的所有葉子節點的權值 \(d_i < k\),並且對於每個點它對周圍邊的貢獻都是確定的(從葉子節點開始確定)所以對於任意一個點 \(u\)
繼續觀察下一檔部分分 \(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;
}