盖世计划--0730--B班训练
A
哈哈,写过的题,看过的性质还能忘,这辈子有了。
一个性质,如果要将 \(A\) 序列通过相邻位置 \(+1\) 或 \(-1\) 操作(总和不变,相当于传递)变为序列 \(B\),设 \(sa_i=\sum\limits_{j=1}^ia_j\),\(sb_i=\sum\limits_{j=1}^ia_j\)。
那么最少操作次数为:
理解一下,从左到右考虑,首先 \(a_1\) 要变成 \(b_1\),需要 \(|a_1-b_1|\) 次操作,\(a_2\) 变成 \(a_2+a_1-b_1\);\(a_2\) 要变成 \(b_2\),需要 \(|a_2+a_1-b_1-b_2|\) 次操作,以此类推就是上式。
然后简单 DP,考场当然想到了,然后没有上面的东西不会转移,哈哈。设 \(f_{i,j,k}\) 表示考虑了前 \(i\) 个位置,\(a_i\) 变成了 \(j\),前缀和为 \(k\) 的最小上式。
这样转移要 \(O(m)\),感觉是 \(O(nm^3)\) 的是吧。但是你发现枚举到 \(i\) 时,\(a_i\) 的上限是 \(\lfloor\frac{m}{i}\rfloor\),不然前面的数就无法比它大了。所以 \(\sum\lfloor\frac{m}{i}\rfloor\approx m\log m\)。
总复杂度为 \(O(nm^2\log m)\),有更好的 \(O(nm^2)\),我不会。
B
可持久线段树维护哈希+二分加速比较字典序
C
简单 DP + 根号分治
D
期望题
E
不会写了哥。
当时看了半天,发现自己平衡树就写了个模板题,能写个寄吧,然后鸽了。然后就考了。
等我训完平衡树凯旋归来,薄纱这道题!
OK,我会了。
每个时刻求出前 \(k\) 大,区间修改平衡树。
具体过程:把前 \(k\) 大 split 出来,打延迟标记,同时看第 \(k\) 大是否为 \(0\),如果是,说明不合法。
然后考虑怎么 merge 回去。你发现第 \(k\) 大的值不一定全都在前 \(k\) 大中,不能直接 merge,那就再把树 split 一下呗。把树分为四部分,就很容易 merge 了。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define mk std::make_pair
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10;
int n, m, cnt, rt, x, y, z, l1, r1, l, r;
int a[N], b[N];
struct node {
int pri, val, sz, l, r, tg;
} t[N];
int getnode(int a) {t[++cnt] = {rand(), a, 1, 0, 0, 0};return cnt;}
void pushup(int u) {
t[u].sz = t[t[u].l].sz + t[t[u].r].sz + 1;
}
void pd(int u) {
if(!t[u].tg) return;
t[t[u].l].val += t[u].tg, t[t[u].l].tg += t[u].tg;
t[t[u].r].val += t[u].tg, t[t[u].r].tg += t[u].tg;
t[u].tg = 0;
}
void split(int u, int &x, int &y, int k) {
if(!u) {x = y = 0; return;}
pd(u);
if(k <= t[t[u].l].sz) {
y = u;
split(t[u].l, x, t[u].l, k);
} else {
x = u;
split(t[u].r, t[u].r, y, k - t[t[u].l].sz - 1);
}
pushup(u);
}
void splitv(int u, int &x, int &y, int v) {
if(!u) {x = y = 0; return;}
pd(u);
if(t[u].val <= v) {
x = u;
splitv(t[u].r, t[u].r, y, v);
} else {
y = u;
splitv(t[u].l, x, t[u].l, v);
}
pushup(u);
}
int merge(int u, int v) {
if(!u || !v) return u ^ v;
if(t[u].pri < t[v].pri) {
pd(u);
t[u].r = merge(t[u].r, v);
pushup(u);
return u;
} else {
pd(v);
t[v].l = merge(u, t[v].l);
pushup(v);
return v;
}
}
void fake_main() {
std::cin >> n >> m;
int s = 0;
for(int i = 1; i <= n; i++) std::cin >> a[i], s += a[i];
for(int i = 1; i <= m; i++) std::cin >> b[i], s -= b[i];
if(s) {
std::cout << "NO\n";
return;
}
std::sort(b + 1, b + m + 1);
rt = cnt = 0;
for(int i = 1; i <= m; i++) {
rt = merge(rt, getnode(b[i]));
}
bool ok = 1;
for(int i = 1; i <= n; i++) {
split(rt, x, y, m - a[i]);
z = y;
while(t[z].l) pd(z), z = t[z].l;
if(!t[z].val) {
ok = 0;
break;
}
t[y].val--, t[y].tg--;
if(!x) {
rt = y;
continue;
}
z = x;
while(t[z].r) pd(z), z = t[z].r;
int p = t[z].val;
splitv(x, l1, r1, p - 1);
splitv(y, l, r, p - 1);
rt = merge(merge(l1, l), merge(r1, r));
}
std::cout << (ok ? "YES\n" : "NO\n");
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T;
std::cin >> T;
while(T--) fake_main();
return 0;
}
F
树形 DP + 背包
总结
算是复习了之前的题,\(7\) 道写过 \(6\) 道就离谱。