題庫連接c++
給定一個整數 \(M\),對於任意一個整數集合 \(S\),定義「校驗值」以下:算法
從集合 \(S\) 中取出 \(M\) 對數(即 \(2\times M\) 個數,不能重複使用集合中的數,若是 \(S\) 中的整數不夠 \(M\) 對,則取到不能取爲止),使得「每對數的差的平方」之和最大,這個最大值就稱爲集合 \(S\) 的「校驗值」。測試
如今給定一個長度爲 \(N\) 的數列 \(A\) 以及一個整數 \(k\)。咱們要把 \(A\) 分紅若干段,使得每一段的「校驗值」都不超過 \(k\)。求最少須要分紅幾段。 多測,測試組數爲 \(T\)。優化
\(T\leq 12,1\leq n,m\leq 5\times 10^5,0\leq k\leq 10^{18},0\leq P_i\leq 2^{20}\)spa
先考慮如何求「校驗值」。思路是當題述結果最大時,應該要求最大值和最小值一組;次大值和次小值一組……code
咱們以 \(4\) 個數爲例,如 \(a,b,c,d\),其中 \(a\leq b\leq c\leq d\) 。那麼 \((d-a)^2+(c-b)^2=a^2+b^2+c^2+d^2-2ad-2bc\),\((b-a)^2+(d-c)^2=a^2+b^2+c^2+d^2-2ab-2cd\)。容易發現 \(ad+bc<ab+cd\),故前者更優。更多數時用數學概括法能夠證實。排序
其次,另一個要點是當一個區間左端點固定時右端點要儘量往右取。ip
注意到這兩點,咱們左端點從 \(1\) 開始,向右找到最遠的符合條件的右端點,劃分爲一段。再接着固定左端點,繼續尋找。這樣便可統計出答案。假設咱們右端點是枚舉獲得的,記答案(最少段數)爲 \(ans\),那麼本算法的複雜度約爲 \(O\left(ans\times\left(\frac{n}{ans}\right)^2\log\frac{n}{ans}\right)=O\left(n^2\times\frac{\log\frac{n}{ans}}{ans}\right)\)。get
若答案趨爲 \(1\),複雜度趨爲 \(O\left(n^2\log n\right)\)。顯然枚舉右端點的思路是過不了此題的。而計算「校驗值」的複雜度是沒法再優化的,考慮如何快速尋找右端點。數學
一個思路是二分右端點,其他同上,能夠證實複雜度是 \(O(n\log^2 n)\) 的。不過惋惜的是出題人把這個算法卡掉了。
既然二分不行,咱們考慮用倍增的思路來求右端點,具體思路是首先設倍增 \(len\) 長度爲 1,若右端點爲 \(r\),判斷若端點 \(r+len\) 符合條件。則將右端點賦值爲 \(r+len\) 而且將 \(len\) 倍增,繼續討論;若不符合條件則將 \(len\) 縮短一半,繼續討論。其實該算法的複雜度與二分是一致的,不過只能寫倍增才能過此題。
另外,不論二分仍是倍增,排序直接 \(\text{sort}\) 依舊過不了。注意到這樣一個小技巧,若是上一個枚舉的右端點爲 \(r\),這一次右端點爲 \(r'\),那麼能夠知道區間 \((l,r)\) 是有序的,那麼咱們只需排序 \((r,r')\) 的區間,以後將兩個部分歸併便可使整個 \((l,r')\) 有序。
#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 5e5+5; void gi(int &x) { x = 0; char ch = getchar(); while (ch < '0' || ch > '9') ch = getchar(); while (ch >= '0' && ch <= '9') x = x*10+ch-'0', ch = getchar(); } int t, n, m, p[N], a[N], kl, kr, tmp[N]; ll k; void merge(int l, int r, int x, int y) { int i = l, j = x, k = l; while (i <= r && j <= y) if (a[i] < a[j]) tmp[k++] = a[i++]; else tmp[k++] = a[j++]; while (i <= r) tmp[k++] = a[i++]; while (j <= y) tmp[k++] = a[j++]; for (int i = l; i <= y; i++) a[i] = tmp[i]; } bool judge(int l, int r) { if (kl == l && r > kr) { for (int i = kr+1; i <= r; i++) a[i] = p[i]; sort(a+kr+1, a+r+1); merge(l, kr, kr+1, r); kr = r; } else { for (int i = l; i <= r; i++) a[i] = p[i]; sort(a+l, a+r+1); kl = l, kr = r; } int t = 0; ll cnt = 0; while (t < m && l < r) { cnt += 1ll*(a[r]-a[l])*(a[r]-a[l]); ++l, ++t, --r; if (cnt > k) return false; } return cnt <= k; } void work() { scanf("%d%d%lld", &n, &m, &k); for (int i = 1; i <= n; i++) gi(p[i]); int l = 1, r = 1, ans = 0, len, k; while (l <= n) { len = 1, k = l; while (len) { if (r+len <= n && judge(l, r+len)) { r += len; k = r, len <<= 1; } else len >>= 1; } l = r = k+1, ++ans; } printf("%d\n", ans); } int main() { scanf("%d", &t); while (t--) work(); return 0; }