小 Z 的手套(gloves)100pts
最大值最小,考慮二分答案;
首先排序,然後每次找出數量較少的那個陣列中的每個數 $ x $ 在另一個陣列中有沒有值在範圍 $ [x - mid, x + mid] $ 的(其中 $ mid $ 為二分的答案),其實只需找 $ x - mid $ 就行,最後判斷一下所有數是否合法即可;
因為已經升序排序,所以可以雙指標維護,當然也可以 lower_bound
,但是多個 $ \log $;
時間複雜度;$ \Theta(n \log Z) $ 到 $ \Theta(n \log Z \log n) $ 不等(其中 $ Z $ 為兩個陣列的極差);
點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n, m;
int a[500005], b[500005];
int vis[500005], vi[500005];
bool ck(int x) {
for (int i = 1; i <= max(n, m); i++) {
vis[i] = 0;
vi[i] = 0;
}
if (n < m) {
int now = lower_bound(b + 1, b + 1 + m, a[1] - x) - b;
for (int i = 1; i <= n; i++) {
int lpos = lower_bound(b + 1, b + 1 + m, a[i] - x) - b;
if (lpos > m) return false;
if (now < lpos) now = lpos;
while(vis[now]) now++;
vis[now] = i;
}
for (int i = 1; i <= m; i++) {
if (vis[i]) {
vi[vis[i]] = true;
if (abs(a[vis[i]] - b[i]) > x) return false;
}
}
for (int i = 1; i <= n; i++) if (!vi[i]) return false;
return true;
} else {
int now = lower_bound(a + 1, a + 1 + n, b[1] - x) - a;
for (int i = 1; i <= m; i++) {
int lpos = lower_bound(a + 1, a + 1 + n, b[i] - x) - a;
if (lpos > n) return false;
if (now < lpos) now = lpos;
while(vis[now]) now++;
vis[now] = i;
}
for (int i = 1; i <= n; i++) {
if (vis[i]) {
vi[vis[i]] = true;
if (abs(b[vis[i]] - a[i]) > x) return false;
}
}
for (int i = 1; i <= m; i++) if (!vi[i]) return false;
return true;
}
}
int main() {
freopen("gloves.in", "r", stdin);
freopen("gloves.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + m);
int l = 0;
int r = 1000000000;
int ans = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if (ck(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << ans;
return 0;
}
小 Z 的字串(string)20pts
DP;
設 $ f_{i, j, k, 0/1/2} $ 表示現在選了 $ i $ 個 $ 0 $, $ j $ 個 $ 1 $ ,$ k $ 個 $ 2 $,當前是 $ 0/1/2 $ 的最小次數;
對於轉移,發現肯定不會換同一個數,所以假設有轉移 $ f_{i, j, k - 1, 0} \rightarrow f_{i, j, k, 2} $,我們只需將第 $ k $ 個數移動到當前位置 $ (i + j + k) $ 即可,然後計算貢獻(注意絕對值),其它同理;
最後答案要除以 $ 2 $,因為假設有兩個數能夠被轉移,它們兩個的相對位置是不變的,也就是說前面的由後面的轉移過來,後面的也由前面的轉移過來,所以要除以 $ 2 $;
時間複雜度:$ \Theta(n^3) $;
點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n;
char s[505];
int t[3][405];
int f[205][205][205][3];
int c[3];
int main() {
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> (s + 1);
n = strlen(s + 1);
for (int i = 1; i <= n; i++) {
t[s[i] - '0'][++c[s[i] - '0']] = i;
}
if (c[0] > (n + 1) / 2 || c[1] > (n + 1) / 2 || c[2] > (n + 1) / 2) {
cout << -1;
return 0;
}
memset(f, 0x3f, sizeof(f));
f[0][0][0][0] = f[0][0][0][1] = f[0][0][0][2] = 0;
for (int i = 0; i <= c[0]; i++) {
for (int j = 0; j <= c[1]; j++) {
for (int k = 0; k <= c[2]; k++) {
int p = i + j + k;
if (p == 0) continue;
if (i) f[i][j][k][0] = min(f[i - 1][j][k][1], f[i - 1][j][k][2]) + abs(p - t[0][i]);
if (j) f[i][j][k][1] = min(f[i][j - 1][k][0], f[i][j - 1][k][2]) + abs(p - t[1][j]);
if (k) f[i][j][k][2] = min(f[i][j][k - 1][0], f[i][j][k - 1][1]) + abs(p - t[2][k]);
}
}
}
cout << min({f[c[0]][c[1]][c[2]][0], f[c[0]][c[1]][c[2]][1], f[c[0]][c[1]][c[2]][2]}) / 2;
return 0;
}
一個真實的故事(truth)50pts
賽時打的 $ \Theta(\frac{nm \log^2 n}{w}) $ 結果算的時候少算倆 $ \log $,所以50pts;
正解就是線段樹;
維護三個東西:
-
答案;
-
從左邊開始的1 ~ k 出現的位置;
-
從右邊開始的1 ~ k 出現的位置;
這樣合併的時候只需將左區間的2和右區間的3合併起來,然後雙指標掃一下即可;
時間複雜度:$ \Theta(nk \log n \log k) $,使用 $ sort $ 時可能會把後面的 $ \log $ 去掉;
點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n, k, m;
int a[500005];
int cnt[35];
pair<int, int> rem[75];
int rcnt;
namespace SEG{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
int l, r, ans, lb[35], rb[35];
}tr[800005];
inline void push_up(int id) {
memset(cnt, 0, sizeof(cnt));
rcnt = 0;
for (int i = 1; i <= k; i++) {
rem[++rcnt] = {tr[ls(id)].rb[i], i};
rem[++rcnt] = {tr[rs(id)].lb[i], i};
}
sort(rem + 1, rem + 1 + rcnt);
int now = 0;
int an = 0x3f3f3f3f;
int pos = 1;
for (int i = 1; i <= rcnt; i++) {
if (rem[i].first) {
pos = i;
break;
}
}
int j = pos;
for (int i = pos; i <= rcnt; i++) {
if (!cnt[rem[i].second]) now++;
cnt[rem[i].second]++;
while(now == k) {
an = min(an, rem[i].first - rem[j].first + 1);
cnt[rem[j].second]--;
if (cnt[rem[j].second] == 0) now--;
j++;
}
}
tr[id].ans = min({an, tr[ls(id)].ans, tr[rs(id)].ans});
for (int i = 1; i <= k; i++) {
if (tr[ls(id)].lb[i]) tr[id].lb[i] = tr[ls(id)].lb[i];
else tr[id].lb[i] = tr[rs(id)].lb[i];
if (tr[rs(id)].rb[i]) tr[id].rb[i] = tr[rs(id)].rb[i];
else tr[id].rb[i] = tr[ls(id)].rb[i];
}
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
tr[id].ans = 0x3f3f3f3f;
if (l == r) {
tr[id].lb[a[l]] = tr[id].rb[a[l]] = l;
if (k == 1) {
if (a[l] == k) tr[id].ans = 1;
}
return;
}
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
push_up(id);
}
void add(int id, int pos, int x) {
if (tr[id].l == tr[id].r) {
tr[id].ans = 0x3f3f3f3f;
if (k == 1) {
if (a[tr[id].l] == k) tr[id].ans = 1;
}
tr[id].lb[x] = tr[id].rb[x] = 0;
tr[id].lb[a[tr[id].l]] = tr[id].rb[a[tr[id].l]] = tr[id].l;
return;
}
int mid = (tr[id].l + tr[id].r) >> 1;
if (pos <= mid) add(ls(id), pos, x);
else add(rs(id), pos, x);
push_up(id);
}
}
using namespace SEG;
int main() {
freopen("truth.in", "r", stdin);
freopen("truth.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
bt(1, 1, n);
int s, p, v;
for (int i = 1; i <= m; i++) {
cin >> s;
if (s == 1) {
cin >> p >> v;
int x = a[p];
a[p] = v;
add(1, p, x);
}
if (s == 2) {
if (tr[1].ans == 0x3f3f3f3f) cout << -1 << '\n';
else cout << tr[1].ans << '\n';
}
}
return 0;
}