CF Round 768 Div2 題解
A題 Min Max Swap
共 \(T\) 組資料。(\(1\leq T \leq 100\))
給定兩個長度為 \(n\) 的數列 \(\{a_n\},\{b_n\}\),你現在可以進行不限量次操作,每次選擇一個 \(p(1\leq p\leq n)\),並交換 \(a_p,b_p\),問怎樣可以使得兩個數列的分別的最大值的乘積最大(不輸出方案,只輸出最大值)?
\(1\leq n\leq 100,1\leq a_i,b_i \leq 10^4\)
有一個顯然的思路:找出最大值所在的陣列,然後讓另外一個數組裡面的值儘可能小即可。
簡化一下,就是:把每個位置上面小的數放在 a 裡面,大的數放在 b 裡面,遍歷一次即可,複雜度 \(O(n)\)
#include<bits/stdc++.h> using namespace std; const int N = 110; int n, a[N], b[N]; int solve() { //read cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; for (int i = 1; i <= n; ++i) cin >> b[i]; //solve for (int i = 1; i <= n; ++i) if (a[i] > b[i]) swap(a[i], b[i]); int Max1 = 0, Max2 = 0; for (int i = 1; i <= n; ++i) Max1 = max(Max1, a[i]), Max2 = max(Max2, b[i]); return Max1 * Max2; } int main() { int T; cin >> T; while (T--) cout << solve() << endl; return 0; }
B題 Fun with Even Subarrays
共 \(T\) 組資料。(\(1\leq T \leq 2*10^4\))
給定一個長度為 \(n\) 的陣列 \(\{a_n\}\),我們可以進行如下操作:
選擇一個長度為 \(2k\) 的區間,然後將後 \(k\) 個數字平移到前 \(k\) 個位置上面(後 \(k\) 個位置上面值不改變),詳情可以見原題。
問,至少需要多少次操作,可以使得整個陣列的值變成一樣?
\(\sum n \leq 2*10^5,1\leq a_i\leq n\)
顯然,最後一個位置的值不可能被變成其他值,那麼我們可以將陣列轉化一下,如果原位置的值等於 \(a_n\)
我們直接貪心:記一個指標 \(p\),從 \(n\) 開始,不斷右移,當 \([p,n]\) 內部不是全 0 時,給 p 加上 1,然後記 \(L=\max(1, 2p-(n+1))\),然後將區間 \([L,p-1]\) 上面全部置為 0,同時 res 加上 1,再讓 \(p=L-1\),直到 \(p=0\) 為止,此時 res 的值就是最少操作次數。
關於怎麼判斷一個區間是不是全 0,以及怎麼快速修改每個位置的值,我考場上套的樹狀陣列,總複雜度 \(O(n\log n)\),其實直接遍歷一遍就行了,複雜度 \(O(n)\)。
//樹狀陣列版本程式碼
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, a[N];
//
int t[N];
inline int lowbit(int x) { return x & (-x); }
int ask(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += t[x];
return res;
}
void add(int x, int val) {
for (; x <= n; x += lowbit(x)) t[x] += val;
}
int query(int l, int r) {
return ask(r) - ask(l - 1);
}
//
void change(int l, int r) {
for (int i = l; i <= r; ++i)
if (query(i, i) == 1) add(i, -1);
}
int solve() {
//read
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
//solve
memset(t, 0, sizeof(int) * (n + 1));
for (int i = 1; i <= n; ++i)
if (a[i] != a[n]) add(i, 1);
int res = 0, p = n - 1;
while (p >= 1) {
if (query(p, n) > 0) {
p++;
int L = max(1, 2 * p - n - 1);
change(L, p - 1);
p = L - 1;
res++;
}
else p--;
}
return res;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) printf("%d\n", solve());
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, a[N];
void change(int l, int r) {
for (int i = l; i <= r; ++i) a[i] = 0;
}
int solve() {
//read
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
//solve
for (int i = 1; i <= n; ++i)
a[i] = a[i] != a[n];
int res = 0, p = n - 1;
while (p >= 1) {
if (a[p] > 0) {
p++;
int L = max(1, 2 * p - n - 1);
change(L, p - 1);
p = L - 1;
res++;
}
else p--;
}
return res;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) printf("%d\n", solve());
return 0;
}
C題 And Matching
有 \(T\) 組資料。(\(1\leq T \leq 400\))
給定 \(n\) 個數 \([0,n)\),保證 \(n\) 是 2 的冪,問能否給這 \(n\) 個數找到一種配對方式,使得 \(\sum\limits_{i=1}^{n/2}a_i\&b_i=k\),並輸出該方案?
\(4\leq n \leq 2^{16},0\leq k < n\)
經過長時間觀察,我發現了一種 \(k=0\) 時的構造方案:\((0,n-1),(1,n-2),\cdots,(\frac{n}{2}-1,\frac{n}{2})\) (這種方案是純觀察到的,但是存在性不容置疑:對每個數,直接尋找按位反差的那個值來配對即可)
如果 \(0 < k < n-1\),我們發現找出下面兩組: \((0,n-1),(k,n-k-1)\),然後交換一下,變為 \((0,n-k-1),(k,n-1)\)即可,別的值不變,這兩組的值之和增加了 \(k\) ,剛好滿足要求。
\(k=n-1\) 的時候,我一開始以為無解,然後 WA 了幾發之後老老實實去找方案:我們選擇 \((0,n-1),(1,n-2)\) 兩組變換為 \((0,1),(n-2,n-1)\),這樣可以增加 \(n-2\);隨後,我們選擇 \((2,n-3),(3,n-4)\) 兩組,變化為 \((2,n-4),(3,n-3)\)。從二進位制上面不難發現,僅有最後一位出現了變化,其他位置保持不變,所以答案增加 1,剛好湊成 \(n-1\),成功。(\(n=4\) 的時候無解,因為此時僅有兩組,不夠分)
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int vis[N];
void solve(int n, int k) {
if (k == n - 1) {
if (n == 4) puts("-1");
else {
printf("%d %d\n%d %d\n%d %d\n%d %d\n", 0, 1, n - 2, n - 1, 2, n - 4, 3, n - 3);
for (int i = 4; i < n / 2; ++i)
printf("%d %d\n", i, n - 1 - i);
}
return;
}
memset(vis, 0, sizeof(int) * (n + 1));
if (k) {
vis[0] = vis[n - 1] = vis[k] = vis[n - k - 1] = 1;
printf("%d %d\n%d %d\n", 0, n - k - 1, k, n - 1);
}
for (int i = 0; i < n / 2; ++i)
if (!vis[i]) printf("%d %d\n", i, n - i - 1);
return;
}
int main()
{
int T;
cin >> T;
while (T--) {
int n, k;
cin >> n >> k;
solve(n, k);
}
return 0;
}
D題 Range and Partition
有 \(T\) 組資料。(\(1\leq T\leq 3*10^4\))
給定一個長度為 \(n\) 的數列 \(\{a_n\}\) 和正整數 \(k\),問能否找到一個區間 \([x,y]\),使得我們能將數列分成恰好 \(k\) 段,每段裡面,在該區間內部的數要比不在該區間的數要多?若存在,請輸出 \(y-x\) 最小值。
\(1\leq k\leq n\leq 2*10^5,1\leq a_i\leq n\)
存在性毋庸置疑:讓這個區間為 \([1,n]\) 即可,然後想怎麼分。
我們先來看看,在確定了區間之後,如何判斷該區間是否合法:我們構建一個新陣列 \(\{b_n\}\),原陣列中在該區間內部的數標記為 1,反之標記為 0,那麼問題就變成了:給定一個 01 陣列,問能否恰好分成 \(k\) 組,每組內部 1 的數量都大於 0?
今年牛客冬令營的第一場 F 題有一個類似的題目,問能否分成若干段,使得每段中位數都大於某個值。那場的題解和標準證明都在這:題解,我們直接搬運一下結論:當區間內 1 的個數減去 0 的個數大於等於 \(k\) 時存在分組方案,直接掃一遍,複雜度 \(O(n)\)。(本題還需要輸出方案,道理也差不多
如果我們暴力列舉 \(x,y\),那麼總複雜度就是 \((n^3)\),如果列舉 \(x\) 並二分 \(y\),那麼複雜度就是 \(O(n^2\log n)\)。
上面因為搬運結論,剛給忘了一個東西:我們並不需要掃一遍整個陣列,只需要先給排個序,然後每次統計數量的時候直接兩次二分即可,這樣複雜度就降到了 \(O(n \log^2 n)\),應該可以通過了。
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, k, a[N], b[N];
bool check(int x, int y) {
int l = lower_bound(a + 1, a + n + 1, x) - a;
int r = upper_bound(a + 1, a + n + 1, y) - a - 1;
return (r - l + 1) - (n - (r - l + 1)) >= k;
}
void output(int x, int y) {
printf("%d %d\n", x, y);
int L = 1;
for (int i = 1; i < k; ++i) {
int cnt = 0;
for (int R = L; R <= n; ++R) {
x <= b[R] && b[R] <= y ? cnt++ : cnt--;
if (cnt > 0) {
printf("%d %d\n", L, R);
L = R + 1;
break;
}
}
}
printf("%d %d\n", L, n);
}
void solve()
{ //read & init
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]), b[i] = a[i];
sort(a + 1, a + n + 1);
//solve
int res = 1e9 + 10, ansx = 0, ansy = 0;
for (int x = 1; x <= n; ++x) {
int l = x, r = n + 1;
while (l < r) {
int mid = (l + r) >> 1;
if (check(x, mid)) r = mid;
else l = mid + 1;
}
if (r != n + 1 && res > r - x) {
res = r - x, ansx = x, ansy = r;
}
}
//output
output(ansx, ansy);
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
不過我們還發現:\(x,y\) 均符合單調性質,所以我們還可以雙指標,將列舉 \(x,y\) 的複雜度降到 \(O(n)\),這樣就可以在 \(O(n\log n)\) 的複雜度內解決該問題了。
其實我們甚至不用直接列舉 \(x,y\),而是直接列舉對應的下標:在排序好的陣列上列舉 \(i,j\),保證 \(i,j\) 之間的數要多於不在該範圍的數,然後不斷更新 \(x=a_i,y=a_j\) 即可。這個 \(i,j\) 甚至不需要雙指標,直接預先算好那個合適的距離 \(len=\lceil \frac{n+k}{2}\rceil\) 即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, k, a[N], b[N];
void output(int x, int y) {
printf("%d %d\n", x, y);
int L = 1;
for (int i = 1; i < k; ++i) {
int cnt = 0;
for (int R = L; R <= n; ++R) {
x <= b[R] && b[R] <= y ? cnt++ : cnt--;
if (cnt > 0) {
printf("%d %d\n", L, R);
L = R + 1;
break;
}
}
}
printf("%d %d\n", L, n);
}
void solve()
{ //read & init
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]), b[i] = a[i];
//solve
sort(a + 1, a + n + 1);
int res = 1e9 + 10, x = 0, y = 0;
int len = (n + k + 1) / 2;
for (int L = 1; L + len - 1 <= n; ++L) {
int R = L + len - 1;
if (res > a[R] - a[L])
res = a[R] - a[L], x = a[L], y = a[R];
}
//output
output(x, y);
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}