《信息学奥赛一本通·高手专项训练》集训 Day 6
luckydrawbox · · 个人记录
树状数组
\color{#52C41A}\text{A. 书籍分配}
题目
现在有
U k a:第k 个书架上书的数量变成a 。Z c s:询问假如(也就是说询问之间互不影响)有s 个人来买书,每个人选择c 个不同的非空书架各买一本书(这会使对应书架上书的数量减一),则是否存在一种方案使得每个人都能买到c 本书。
题解
有个很显然的贪心思想是,让这
这个常识般的思维给我们的发现是,尽量拿书本数量
对于后者的证明:设书本数量
因此,设书本数量
用权值树状数组维护每个数值的出现次数和数值和即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e6 + 10;
int n, m;
struct que {
int opt;
ll x, y, y_;
} q[N];
ll c1[N], c2[N], b[N], tot, a[N], d[N];
void add1(int x, ll v) {
for (; x <= tot; x += x & -x) c1[x] += v;
}
void add2(int x, ll v) {
for (; x <= tot; x += x & -x) c2[x] += v;
}
ll ask1(int x) {
ll ans = 0;
for (; x; x -= x & -x) ans += c1[x];
return ans;
}
ll ask2(int x) {
ll ans = 0;
for (; x; x -= x & -x) ans += c2[x];
return ans;
}
int main() {
freopen("book.in", "r", stdin);
freopen("book.out", "w", stdout);
n = read();
m = read();
for (int i = 1; i <= m; i++) {
char op;
ll x, y;
cin >> op;
q[i].x = read();
q[i].y = read();
b[i] = q[i].y;
q[i].opt = (op == 'Z');
}
b[m + 1] = 0;
sort(b + 1, b + m + 2);
tot = unique(b + 1, b + m + 2) - b - 1;
for (int i = 1; i <= m; i++)
q[i].y_ = lower_bound(b + 1, b + tot + 1, q[i].y) - b;
for (int i = 1; i <= n; i++) {
a[i] = 1;
add1(1, 1);
}
for (int i = 1; i <= m; i++) {
if (q[i].opt == 0) {
add1(a[q[i].x], -1);
add2(a[q[i].x], -d[q[i].x]);
a[q[i].x] = q[i].y_;
d[q[i].x] = q[i].y;
add1(a[q[i].x], 1);
add2(a[q[i].x], d[q[i].x]);
} else {
if (ask2(tot) < q[i].x * q[i].y) {
puts("NIE");
continue;
}
ll c_ = q[i].x - (ask1(tot) - ask1(q[i].y_ - 1));
if (ask2(q[i].y_ - 1) >= c_ * q[i].y)
puts("TAK");
else
puts("NIE");
}
}
return 0;
}
\color{#3498DB}\text{B.最优团队}
题目
有
定义一个团队由一个队长与若干个队员组成,其中队员人数要至少有一人,队长必须有且只能有一人。
设队长的编号为
其中
有
题解
我们发现每个人做队长可以带的最多的人数是固定的,于是可以预处理出每个人做队长可以带的最多的人数。具体来说,按
然后考虑询问,对于一组询问
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 4e5 + 10;
int n, k, r[N], a[N], m, res[N], head[N], nxt[N];
int c[N], b[N], t, d[N], ma[N], ans[N];
vector<int> e[N];
struct asdf {
int x, y;
} q[N];
bool cmp1(int x, int y) { return r[x] < r[y]; }
void add(int x, int v) {
for (; x <= t; x += x & -x) c[x] += v;
}
int ask(int x) {
int ans = 0;
for (; x; x -= x & -x) ans += c[x];
return ans;
}
#define pl p << 1
#define pr p << 1 | 1
#define S_T_T int
struct Segment_Tree {
struct Tree {
int l, r;
S_T_T val;
} a[N * 4];
void pushup(int p) { a[p].val = max(a[pl].val, a[pr].val); }
void build(int p, int l, int r) {
a[p].l = l;
a[p].r = r;
if (l == r) {
a[p].val = 0;
return;
}
int mid = (l + r) >> 1;
build(pl, l, mid);
build(pr, mid + 1, r);
pushup(p);
}
void change(int p, int x, S_T_T v) {
if (a[p].l == a[p].r) {
a[p].val = max(a[p].val, v);
return;
}
int mid = (a[p].l + a[p].r) >> 1;
if (x <= mid)
change(pl, x, v);
if (x > mid)
change(pr, x, v);
pushup(p);
}
S_T_T ask(int p, int l, int r) {
if (l <= a[p].l && a[p].r <= r) {
return a[p].val;
}
int mid = (a[p].l + a[p].r) >> 1;
S_T_T ans = 0;
if (l <= mid)
ans = max(ans, ask(pl, l, r));
if (r > mid)
ans = max(ans, ask(pr, l, r));
return ans;
}
} tree;
int main() {
freopen("group.in", "r", stdin);
freopen("group.out", "w", stdout);
n = read();
k = read();
for (int i = 1; i <= n; i++) {
b[++t] = r[i] = read();
d[i] = i;
}
sort(b + 1, b + t + 1);
t = unique(b + 1, b + t + 1) - b - 1;
for (int i = 1; i <= n; i++) r[i] = lower_bound(b + 1, b + t + 1, r[i]) - b;
t = 0;
for (int i = 1; i <= n; i++) {
a[i] = read();
b[++t] = a[i];
b[++t] = a[i] - k;
b[++t] = a[i] + k;
}
sort(b + 1, b + t + 1);
t = unique(b + 1, b + t + 1) - b - 1;
for (int i = 1; i <= n; i++) {
head[i] = lower_bound(b + 1, b + t + 1, a[i] - k) - b;
nxt[i] = lower_bound(b + 1, b + t + 1, a[i] + k) - b;
a[i] = lower_bound(b + 1, b + t + 1, a[i]) - b;
}
sort(d + 1, d + n + 1, cmp1);
for (int i = 1; i <= n; i++) {
int j = i;
while (j + 1 <= n && r[d[j + 1]] == r[d[i]]) j++;
for (int p = i; p <= j; p++) add(a[d[p]], 1);
for (int p = i; p <= j; p++)
res[d[p]] = ask(nxt[d[p]]) - ask(head[d[p]] - 1);
i = j;
}
tree.build(1, 1, t);
m = read();
for (int i = 1; i <= m; i++) {
int x, y;
x = read();
y = read();
if (a[x] > a[y])
swap(x, y);
q[i].x = x;
q[i].y = y;
e[max(r[x], r[y])].push_back(i);
}
for (int i = n; i >= 1; i--) {
int j = i;
while (j - 1 >= 1 && r[d[j - 1]] == r[d[i]]) j--;
for (int p = i; p >= j; p--) tree.change(1, a[d[p]], res[d[p]]);
for (int p = 0; p < e[r[d[i]]].size(); p++) {
int w = e[r[d[i]]][p];
if (nxt[q[w].x] < head[q[w].y])
ans[w] = -1;
else
ans[w] = tree.ask(1, head[q[w].y], nxt[q[w].x]);
if (ans[w] < 2)
ans[w] = -1;
}
i = j;
}
for (int i = 1; i <= m; i++) {
write(ans[i]);
putchar('\n');
}
return 0;
}
\color{#9D3DCF}\text{C. 拆分集合}
题目
给定一个有
题解
设
我们发现如果是连续选择几个数在某个集合,则会进行冗余的计算,只有选择的数被放在的集合不一样时才更有意义,也就是“断点”位置更有用,于是设
这个转移式算起来仍是
每个式子前面的部分显然可以用树状数组维护。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 5e5 + 10;
int n;
ll h[N], b[N], c1[N], c2[N], s[N], ans, tot;
void add1(int x, ll v) {
for (; x <= tot; x += x & -x) c1[x] = min(c1[x], v);
}
void add2(int x, ll v) {
for (; x <= tot; x += x & -x) c2[x] = min(c2[x], v);
}
ll ask1(int x) {
ll ans = 1e17;
for (; x; x -= x & -x) ans = min(ans, c1[x]);
return ans;
}
ll ask2(int x) {
ll ans = 1e17;
for (; x; x -= x & -x) ans = min(ans, c2[x]);
return ans;
}
int main() {
freopen("sprung.in", "r", stdin);
freopen("sprung.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) {
b[i] = h[i] = read();
s[i] = s[i - 1] + abs(h[i] - h[i - 1]);
}
memset(c1, 0x3f, sizeof(c1));
memset(c2, 0x3f, sizeof(c2));
sort(b + 1, b + n + 1);
tot = unique(b + 1, b + n + 1) - b;
ans = s[n];
add1(1, 0);
add2(tot + 1, 0);
for (int i = 2; i <= n; i++) {
int h_ = lower_bound(b + 1, b + tot, h[i]) - b + 1;
ll g = min(ask1(h_) + h[i], ask2(tot - h_ + 1) - h[i]) + s[i - 1];
ans = min(ans, g + s[n] - s[i]);
h_ = lower_bound(b + 1, b + tot, h[i - 1]) - b + 1;
add1(h_, g - s[i] - h[i - 1]);
add2(tot - h_ + 1, g - s[i] + h[i - 1]);
}
write(ans);
return 0;
}
RMQ 问题
\color{#9D3DCF}\text{A. 数列求值}
题目
给定一个长度为
给定
连续极长标记区间:我们称区间
本题强制在线。
题解
我们可以从小到大枚举
具体来说,我们可以开个线段树维护当前被标记的数的连续极长标记区间的个数和长度平方和,每次
对于更新后的答案,我们再开一个线段树,维护以区间个数为下标,区间的最大得分,在线回答询问即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 3e6 + 10;
int n, t;
ll a[N], k = 1;
vector<int> e[N];
struct ANS {
ll s, k;
} ans[N], tmp;
bool cmp(ANS x, ANS y) {
if (x.s == -1)
return 1;
if (y.s == -1)
return 0;
if (x.s * y.k == y.s * x.k)
return x.k < y.k;
return x.s * y.k < y.s * x.k;
}
#define pl p << 1
#define pr p << 1 | 1
#define S_T_T ll
struct Segment_Tree {
struct Tree {
int l, r;
S_T_T sum, lmx, rmx, cnt;
} a[N * 4], d;
void pushup(int p) {
a[p].lmx = a[pl].lmx;
if (a[pl].lmx == a[pl].r - a[pl].l + 1)
a[p].lmx += a[pr].lmx;
a[p].rmx = a[pr].rmx;
if (a[pr].rmx == a[pr].r - a[pr].l + 1)
a[p].rmx += a[pl].rmx;
a[p].sum = a[pl].sum + a[pr].sum;
a[p].cnt = a[pl].cnt + a[pr].cnt;
if (a[pl].rmx && a[pr].lmx) {
a[p].sum += 2 * a[pl].rmx * a[pr].lmx;
a[p].cnt--;
}
}
void pushdown(int p) { ; }
void build(int p, int l, int r) {
a[p].l = l;
a[p].r = r;
if (l == r) {
a[p].sum = a[p].lmx = a[p].rmx = a[p].cnt = 0;
return;
}
int mid = (l + r) >> 1;
build(pl, l, mid);
build(pr, mid + 1, r);
pushup(p);
}
void change(int p, int x, S_T_T v) {
if (a[p].l == a[p].r) {
a[p].sum = a[p].lmx = a[p].rmx = a[p].cnt = v;
return;
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
if (x <= mid)
change(pl, x, v);
if (x > mid)
change(pr, x, v);
pushup(p);
}
Tree ask(int p, int l, int r) {
if (l <= a[p].l && a[p].r <= r) {
return a[p];
}
pushdown(p);
int mid = (a[p].l + a[p].r) >> 1;
if (l <= mid && r > mid) {
Tree ans, tl = ask(pl, l, r), tr = ask(pr, l, r);
ans.l = tl.l;
ans.r = tr.r;
ans.lmx = tl.lmx;
if (tl.lmx == tl.r - tl.l + 1)
ans.lmx += tr.lmx;
ans.rmx = tr.rmx;
if (tr.rmx == tr.r - tr.l + 1)
ans.rmx += tl.rmx;
ans.sum = tl.sum + tr.sum;
ans.cnt = tl.cnt + tr.cnt;
if (tl.rmx && tr.lmx) {
ans.sum += 2 * tl.rmx * tr.lmx;
ans.cnt--;
}
return ans;
}
if (l <= mid)
return ask(pl, l, r);
if (r > mid)
return ask(pr, l, r);
}
} tree;
struct Segment_Tree2 {
struct Tree2 {
int l, r;
ANS mx;
} a[N * 4];
ANS max_(ANS x, ANS y) {
if (cmp(x, y))
return y;
else
return x;
}
void pushup(int p) { a[p].mx = max_(a[pl].mx, a[pr].mx); }
void build(int p, int l, int r) {
a[p].l = l;
a[p].r = r;
if (l == r)
return;
int mid = (l + r) >> 1;
build(pl, l, mid);
build(pr, mid + 1, r);
pushup(p);
}
void change(int p, int x, ANS v) {
if (a[p].l == a[p].r) {
a[p].mx = v;
return;
}
int mid = (a[p].l + a[p].r) >> 1;
if (x <= mid)
change(pl, x, v);
if (x > mid)
change(pr, x, v);
pushup(p);
}
ANS ask(int p, int l, int r) {
if (l <= a[p].l && a[p].r <= r)
return a[p].mx;
int mid = (a[p].l + a[p].r) >> 1;
ANS ans;
ans.k = -1;
ans.s = -1;
if (l <= mid)
ans = max_(ans, ask(pl, l, r));
if (r > mid)
ans = max_(ans, ask(pr, l, r));
return ans;
}
} tree2;
int main() {
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
n = read();
t = read();
tree.build(1, 1, n);
for (int i = 1; i <= n; i++) {
a[i] = read();
k = max(k, a[i]);
e[a[i]].push_back(i);
ans[i].s = ans[i].k = -1;
}
for (int i = 1; i <= k; i++) {
for (int j = 0; j < e[i].size(); j++) tree.change(1, e[i][j], 1);
tree.d = tree.ask(1, 1, n);
tmp.s = tree.d.sum;
tmp.k = i;
if (!cmp(tmp, ans[tree.d.cnt]))
ans[tree.d.cnt] = tmp;
}
tree2.build(1, 1, n);
for (int i = 1; i <= n; i++)
tree2.change(1, i, ans[i]);
ll lastans = 0;
for (int i = 1; i <= t; i++) {
ll a, b, x, y, l, r;
a = read();
b = read();
x = read();
y = read();
l = (a * lastans + x - 1) % n + 1;
r = (b * lastans + y - 1) % n + 1;
if (l > r)
swap(l, r);
ANS c = tree2.ask(1, l, r);
if (c.s == -1) {
puts("-1 -1");
lastans = 1;
continue;
}
printf("%lld %lld\n", c.s, c.k);
lastans = c.s % n * c.k % n;
}
return 0;
}
\color{#9D3DCF}\text{B. 最优序列}
题目
给定一个长度为
再给定两个正整数
对于所有的
题解
其实
题目
有
题解
题意转化下就是求第
于是就可以用
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e5 + 10, M = 4e5 + 10;
int n, m, fm, q;
int dep[N], pos[N], f[M][20], Log[M];
int head[N], ver[M << 1], nxt[M << 1], edge[M << 1], tot;
ll dis[N];
void add(int x, int y, int z) {
ver[++tot] = y;
edge[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
int lca(int x, int y) {
x = pos[x];
y = pos[y];
if (x > y)
swap(x, y);
int k = Log[y - x + 1];
if (dep[f[x][k]] < dep[f[y - (1 << k) + 1][k]])
return f[x][k];
return f[y - (1 << k) + 1][k];
}
struct path {
int x, y;
path() {}
path(int x_, int y_) : x(x_), y(y_) {}
} g[N][20];
bool cmp(int x, int y) { return dep[x] > dep[y]; }
path merge(path a, path b) {
int c[4];
c[0] = lca(a.x, b.x);
c[1] = lca(a.x, b.y);
c[2] = lca(a.y, b.x);
c[3] = lca(a.y, b.y);
sort(c, c + 4, cmp);
return path(c[0], c[1]);
}
ll ask_path(int x, int y) {
int k = Log[y - x + 1];
path p = merge(g[x][k], g[y - (1 << k) + 1][k]);
return dis[p.x] + dis[p.y] - 2 * dis[lca(p.x, p.y)];
}
void dfs(int x, int fa) {
dep[x] = dep[fa] + 1;
f[++fm][0] = x;
pos[x] = fm;
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (y == fa)
continue;
dis[y] = dis[x] + edge[i];
dfs(y, x);
f[++fm][0] = x;
}
}
int main() {
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
Log[0] = -1;
for (int i = 1; i < M; i++) Log[i] = Log[i >> 1] + 1;
n = read();
for (int i = 1; i < n; i++) {
int x, y, z;
x = read();
y = read();
z = read();
add(x, y, z);
add(y, x, z);
}
dfs(1, 0);
for (int j = 1; j <= Log[fm]; j++)
for (int i = 1; i + (1 << j) - 1 <= fm; i++) {
if (dep[f[i][j - 1]] < dep[f[i + (1 << (j - 1))][j - 1]])
f[i][j] = f[i][j - 1];
else
f[i][j] = f[i + (1 << (j - 1))][j - 1];
}
m = read();
for (int i = 1; i <= m; i++) {
int u, v;
u = read();
v = read();
g[i][0] = path(u, v);
}
for (int j = 1; j <= Log[m]; j++)
for (int i = 1; i + (1 << j) - 1 <= m; i++)
g[i][j] = merge(g[i][j - 1], g[i + (1 << (j - 1))][j - 1]);
q = read();
for (int i = 1; i <= q; i++) {
int l, r;
l = read();
r = read();
write(ask_path(l, r));
putchar('\n');
}
return 0;
}