模拟赛 1.15 题解
感觉有点打的太糖了,感觉写之前应该先评估一下自己写的是一坨什么大分,如果太大分了以出题人的性格他自己都不会吃大分的情况下肯定是我做麻烦了。
T1 糖
题意:现在有 \(n\) 个桶,每个桶初始有 \(a_i\) 个红球,\(b_i\) 个白球,除了最后一个桶没有容量上限外,其他的桶都有一个 \(c_i\) 的上限,保证 \(c\) 是偶数。
现在有两种操作:
-
给一个桶加一个颜色的球。如果一个桶里的球超出了容量限制,那么我们让数量较多的那种球拿一个出来放到下一个桶里,如果再超出继续拿出。
-
询问一个桶里有红蓝球分别有多少个。
\(n,q\le 2\times 10^5\)。
做法:
首先发现一个桶有三个状态:没放满;放满了,但是还没有红蓝球一样多;放满了,红蓝球一样多。
考虑对这三种状态放球的效果,第一种就是直接把这个球吃下来;第二种如果给的是较少的,就把这个球吃下来,并且把较多的扔出去等量的球;第三种无论给什么球都会出去什么球。
那么直接维护一下,第一种用 set 存起来,第二种应该找到一条红蓝交替的链,假设这次操作放了 \(v\) 个,就要满足这条链较少的至少比一半差了 \(v\) 个,然后链顶要差的比 \(v\) 少,这个就暴力改成第三种状态,是均摊对的。第三种直接删掉就可以了。这个东西可以用 LCT 直接瞎维护,也可以注意到这个树是一条链每个点挂菊花的形状,也可以用平衡树维护。
做到这里你觉得很有道理,然后嘎嘎开写然后你就发现细节一坨然后倒闭了!像我最后写了 9k 仍然没调明白。。。主播主播,有没有什么又简单又强势的做法呢,有的兄弟有的。
我们考虑题目没有要求在线那就是可以离线,我们考虑换维,原本是扫时间维维护序列维,现在我们考虑 \(1\to n\) 扫描序列维,维护时间维每个时间被加入了多少红蓝球,初始的就是时间 \(0\) 加入的。然后我们发现,我们可以直接计算红蓝球分别有多少个,最后再计算保留的情况而不用那么困难地维护现在有多少个,那么询问就很容易回答了,询问前缀红/蓝球个数即可。那么如何转移到 \(i+1\) 的桶呢?我们先把前 \(c_i\) 个球清空,他们全部被收下来,然后考虑我需要把若干个球反转颜色,这个都可以简单地用线段树维护一下即可。
复杂度 \(O(n\log n)\),代码十分好写。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2e5 + 5;
struct node {
int x, y, s;
friend node operator+(node x, node y) {
return node{x.x + y.x, x.y + y.y, x.s + y.s};
}
} ;
struct Segtree {
node tr[maxn << 2];
int tag[maxn << 2], to[maxn << 2];
void pushup(int t) {
tr[t] = tr[t << 1] + tr[t << 1 | 1];
}
void addtag(int t) {
tag[t] = 1;
tr[t] = {0, 0, 0};
}
void addtagt(int t, int v) {
to[t] = v;
if(v == 1)
tr[t].x += tr[t].y, tr[t].y = 0;
if(v == 2)
tr[t].y += tr[t].x, tr[t].x = 0;
}
void pushdown(int t) {
if(tag[t]) {
addtag(t << 1), addtag(t << 1 | 1);
tag[t] = 0;
}
if(to[t])
addtagt(t << 1, to[t]), addtagt(t << 1 | 1, to[t]),
to[t] = 0;
}
void modify(int l, int r, int pos, int t, node v) {
if(l == r) {
tr[t] = tr[t] + v;
return ;
}
pushdown(t);
int mid = l + r >> 1;
if(pos <= mid)
modify(l, mid, pos, t << 1, v);
else
modify(mid + 1, r, pos, t << 1 | 1, v);
pushup(t);
}
int rx, ry;
void query1(int l, int r, int t, int lim) {
if(l == r) {
int d = min(tr[t].x, lim);
tr[t].x -= d, lim -= d, tr[t].s -= d;
rx += d;
d = min(tr[t].y, lim);
tr[t].y -= d, lim -= d, tr[t].s -= d;
ry += d;
return ;
}
pushdown(t);
int mid = l + r >> 1;
// cout << l << " " << r << " " << tr[t].s << " debug" << lim << endl;
if(tr[t << 1].s >= lim)
query1(l, mid, t << 1, lim);
else {
rx += tr[t << 1].x;
ry += tr[t << 1].y;
query1(mid + 1, r, t << 1 | 1, lim - tr[t << 1].s);
addtag(t << 1);
}
pushup(t);
// cout << l << " " << r << " " << tr[t].s << endl;
}
int queryk(int l, int r, int x, int y, int t, int k) {
if(x <= l && r <= y)
return (k == 1 ? tr[t].x : tr[t].y);
int mid = l + r >> 1;
pushdown(t);
if(y <= mid)
return queryk(l, mid, x, y, t << 1, k);
if(mid < x)
return queryk(mid + 1, r, x, y, t << 1 | 1, k);
return queryk(l, mid, x, y, t << 1, k) + queryk(mid + 1, r, x, y, t << 1 | 1, k);
}
void query2(int l, int r, int t, int lim, int k) {
if(l == r) {
if(k == 1) {
int d = min(lim, tr[t].x);
tr[t].x -= d, tr[t].y += d;
}
else {
int d = min(lim, tr[t].y);
tr[t].y -= d, tr[t].x += d;
}
return ;
}
pushdown(t);
int mid = l + r >> 1;
int res = (k == 1 ? tr[t << 1].x : tr[t << 1].y);
if(lim <= res)
query2(l, mid, t << 1, lim, k);
else {
addtagt(t << 1, 3 - k);
query2(mid + 1, r, t << 1 | 1, lim - res, k);
}
pushup(t);
}
void out(int l, int r, int t) {
cout << l << " " << r << " " << tr[t].x << " " << tr[t].y << " " << tr[t].s << endl;
if(l == r)
return ;
pushdown(t);
int mid = l + r >> 1;
out(l, mid, t << 1), out(mid + 1, r, t << 1 | 1);
}
} tree;
struct opt {
int p; node v;
};
vector<opt> v[maxn];
vector<int> qry[maxn];
int c[maxn], n, q, ans[maxn][2], use[maxn];
signed main() {
// freopen("test.in", "r", stdin);
// freopen("std.out", "w", stdout);
cin >> n >> q;
for (int i = 1; i < n; i++) {
int a, b; cin >> a >> b >> c[i];
v[i].push_back(opt{0, node{a, b, a + b}});
}
int a, b; cin >> a >> b; c[n] = 1e18;
v[n].push_back(opt{0, {a, b, a + b}});
for (int i = 1; i <= q; i++) {
int op, x, vl;
cin >> op >> x;
if(op == 1)
cin >> vl, v[x].push_back(opt{i, node{vl, 0, vl}});
if(op == 2)
cin >> vl, v[x].push_back(opt{i, node{0, vl, vl}});
if(op == 3)
qry[x].push_back(i), use[i] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < v[i].size(); j++)
tree.modify(0, q, v[i][j].p, 1, v[i][j].v);
// if(i == 1)
// tree.out(0, q, 1);
for (int j = 0; j < qry[i].size(); j++) {
int x = tree.queryk(0, q, 0, qry[i][j], 1, 1), y = tree.queryk(0, q, 0, qry[i][j], 1, 2);
if(x + y >= c[i]) {
if(x < c[i] / 2)
y = c[i] - x;
else if(y < c[i] / 2)
x = c[i] - y;
else
x = y = c[i] / 2;
}
ans[qry[i][j]][0] = x, ans[qry[i][j]][1] = y;
// cout << i << " " << qry[i][j] << "Adasdgj" << endl;
}
tree.rx = tree.ry = 0;
// cout << i << " " << tree.tr[1].x << " " << tree.tr[1].y << " ?" << tree.tr[1].s << endl;
tree.query1(0, q, 1, c[i]);
// cout << i << " " << tree.tr[1].x << " " << tree.tr[1].y << " ?" << tree.tr[1].s << endl;
tree.query2(0, q, 1, abs(c[i] / 2 - tree.rx), (tree.rx < tree.ry ? 1 : 2));
// cout << i << " " << tree.tr[1].x << " " << tree.tr[1].y << " ?" << tree.tr[1].s << endl;
}
for (int i = 1; i <= q; i++) {
if(use[i])
cout << ans[i][0] << " " << ans[i][1] << endl;
}
return 0;
}
T2 回
题意:现在有一个下标从 \(0\) 开始的 \((n+2)(n+2)\) 大小的棋盘,有 \(k\) 个 \(1\le x,y\le n\) 被操作,保证被操作的位置互不相同,每次操作会令每个格子 \((x_0,y_0)\) 加上 \(\max(|x_0-x|,|y_0-y|)\)。现在不告诉你这些操作的位置,你可以询问每个格子的值,交互库会告诉你答案。
交互库没有次数限制,会以 \(O(n+k)\) 的时间预处理并 \(O(1)\) 回答你的询问。\(n,k\le 5\times 10^5\)。
做法:
感觉无从下手。但是注意到交互库可以在 \(O(n+k)\) 的时间预处理,那么按理来说,信息量应该也只有 \(O(n+k)\)。
这里有两个想法,一个是同学说的,一个是 dls 讲的。
第一个做法是把切比雪夫距离转化成曼哈顿距离,然后发现我如果问两个相邻的格子,就可以算出来 \(\le x'\) 和 \(> x'\) 的格子个数之差,这样就可以解出来每行每列有多少次操作,当然这里的行列是斜着的,然后就变成,比如 \((1,1)\to (1,1)\) 这一斜和 \((n, n)\to(n,n)\) 这两斜,都只能和 \((1,1)\to (n,n)\) 这一斜进行一个匹配,放上一个。就是一个匹配状物,并且你可以把放的位置少的主斜放在上面,发现是一个包含区间的关系。
那么就可以直接贪心,从上往下做,每次把剩余度数最多的不重复的副斜拉出来去匹配即可,复杂度 \(O(n\log n)\)。
第二个做法感觉比较巧妙。因为我们考虑这是对整个矩形做一个加法,我们考虑去做一个类似二维差分一样的东西,这样会方便我们理解这个问题的本质。我们这里令 \(X_{i,j} = (A_{i,j-1}+A_{i,j+1}+A_{i-1,j}+A_{i+1,j}-4A_{i,j})\),发现对于经过 \((x,y)\) 的两条主副对角线都会 \(+2\),\((x,y)\) 这个位置刚好就是加两次,为了方便这里可以把 \(X\) 再除掉 \(2\)。
那么我们就可以询问若干个 \((x,y)\) 的答案使得所有对角线线性相关,再加上主副对角线之和相等,可以解出来每个主副对角线的操作次数,还是和上面一样做匹配即可。
因为这个题方格和斜坐标转化有点太邪恶了写不明白,代码就咕咕咕了。
T3 棋
题意:给出一棵树,点 \(u\) 上有 \(b_u\) 个棋子,定义一组匹配是 \(x,y\) 上的棋子,要求 \(x\) 是 \(y\) 在树上的祖先,贡献是 \(a_y-a_x\),每个棋子只能被匹配一次,求对于每个节点为根的最大贡献。\(n\le 2\times 10^5\)。
做法:
首先考虑对于固定根怎么做。考虑 slope trick,记 \(dp_{u,i}\) 代表 \(u\) 子树内存在多少个需要向上匹配的点,\(dp_{u,i}\) 可以转移到 \(dp_{u,i+[-b_u,b_u]}\),还要加上对应系数的权值。然后把凸壳画出来,发现这里就是先加入 \(2b_u\) 个斜率,左端需要弹出 \(b_u\) 个 \(<0\) 的部分并加上斜率以补回来截距,或者考虑反悔贪心,最后可以得到这么一个代码:
dfs(u):
pq[u] 插入 2 * b[u] 个 a[u] // 大根堆
for v in child[u]:
dp[u] += dp[v]
pq[u] = merge(pq[u], pq[v])
dp[u] += pq[u] 的前 b[u] 大 - a[u] * b[u]
pq[u] 弹出前 b[u] 大
这个直接做就可以解决一个根的问题了。
考虑换根解决所有根的问题。先进行一次 dfs 算出来根的答案,我们对于当前 \(u\) 这个树保留目前还没有删掉 \(b_u\) 个最大值时的树,然后在上传到父亲的时候再删除掉并和父亲合并。
换根时,假设现在树根为 \(rt\),且目前也没有把前 \(b_{rt}\) 个给干掉,可以在线段树上简单二分求出来 \(rt\) 的贡献。考虑怎么从 \(u\to v\)。我们要做两步事情:
-
删除 \(u\) 上面和其他子树的并中前 \(b_u\) 大并算入贡献。
-
加入 \(v\) 子树内删除的 \(b_v\) 大。
第二个操作是简单的,我们可以在预处理的时候记下来然后暴力插回去,次数总数是 \(O(n)\) 的。关键是第一个怎么处理。
我们考虑可持久线段树是可以做一个类似差分的东西的,所以我们考虑用 \(rt\) 这棵树和 \(v\) 这棵树同时二分,计算出前 \(b_u\) 大并删除。计算比较简单,关键是怎么删除还要保留 \(v\) 里面的,我们不可能做线段树合并,这个复杂度并没有保证。
这里有一个很厉害的做法,当我们的右儿子个数不够的时候,我们就直接把当前根的右儿子换成 \(v\) 的右儿子就可以了,如果保留左儿子就用直接 \(rt\) 的左儿子即可,可以算一算是对的。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2e5 + 5;
int n, op;
vector<int> e[maxn];
int a[maxn], b[maxn], ls[maxn], tot;
struct node {
int l, r;
__int128 sum;
int cnt;
} ;
__int128 ans = 0;
struct Segtree {
int rt[maxn], tot;
__int128 ans;
node tr[maxn * 200];
void pushup(int t) {
tr[t].sum = tr[tr[t].l].sum + tr[tr[t].r].sum;
tr[t].cnt = tr[tr[t].l].cnt + tr[tr[t].r].cnt;
}
int modify(int l, int r, int pos, int t, int q, int cnt) {
t = ++tot, tr[t] = tr[q];
if(l == r) {
tr[t].cnt += cnt;
tr[t].sum += cnt * ls[l];
return t;
}
int mid = l + r >> 1;
if(pos <= mid)
tr[t].l = modify(l, mid, pos, tr[t].l, tr[q].l, cnt);
else
tr[t].r = modify(mid + 1, r, pos, tr[t].r, tr[q].r, cnt);
pushup(t);
return t;
}
int mrg(int l, int r, int x, int y) {
if(!x || !y)
return x + y;
int p = ++tot;
if(l == r) {
tr[p].sum = tr[x].sum + tr[y].sum;
tr[p].cnt = tr[x].cnt + tr[y].cnt;
return p;
}
int mid = l + r >> 1;
tr[p].l = mrg(l, mid, tr[x].l, tr[y].l);
tr[p].r = mrg(mid + 1, r, tr[x].r, tr[y].r);
pushup(p);
return p;
}
pair<int, int> get_top(int l, int r, int t) {
if(l == r)
return make_pair(l, tr[t].cnt);
int mid = l + r >> 1;
if(tr[tr[t].r].cnt)
return get_top(mid + 1, r, tr[t].r);
return get_top(l, mid, tr[t].l);
}
int queryp(int l, int r, int p, int q, int rt, int lim) {
// cout << l << " " << r << " " << lim << " " << tr[p].cnt << " " << tr[q].cnt << " " << tr[tr[q].r].cnt << endl;
if(l == r) {
rt = ++tot;
tr[rt].sum = (tr[p].cnt - lim) * ls[l], tr[rt].cnt = tr[p].cnt - lim;
ans += lim * ls[l];
return rt;
}
int mid = l + r >> 1;
rt = ++tot;
if(tr[tr[p].r].cnt - tr[tr[q].r].cnt >= lim)
tr[rt].r = queryp(mid + 1, r, tr[p].r, tr[q].r, tr[rt].r, lim),
tr[rt].l = tr[p].l;
else
tr[rt].r = tr[q].r,
ans += tr[tr[p].r].sum - tr[tr[q].r].sum,
tr[rt].l = queryp(l, mid, tr[p].l, tr[q].l, tr[rt].l, lim - (tr[tr[p].r].cnt - tr[tr[q].r].cnt));
pushup(rt);
return rt;
}
__int128 queryk(int l, int r, int p, int lim) {
if(!p)
return 0;
if(l == r)
return min(tr[p].cnt, lim) * ls[l];
int mid = l + r >> 1;
if(tr[tr[p].r].cnt >= lim)
return queryk(mid + 1, r, tr[p].r, lim);
return queryk(l, mid, tr[p].l, lim - tr[tr[p].r].cnt) + tr[tr[p].r].sum;
}
void out(int l, int r, int t) {
// cout << l << " " << r << " " << tr[t].sum << " " << tr[t].cnt << endl;
if(l == r)
return ;
int mid = l + r >> 1;
out(l, mid, tr[t].l), out(mid + 1, r, tr[t].r);
}
} tree;
__int128 dp[maxn];
void write(__int128 x) {
if(x <= 9) {
putchar(x + '0');
return ;
}
write(x / 10);
putchar(x % 10 + '0');
}
struct opt {
int pos, v;
};
vector<opt> vec[maxn];
void dfs(int u, int fa) {
tree.rt[u] = tree.modify(1, tot, a[u], tree.rt[u], tree.rt[u], 2 * b[u]);
int son = 0;
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if(v == fa)
continue;
dfs(v, u); son++;
tree.rt[u] = tree.mrg(1, tot, tree.rt[u], tree.rt[v]);
dp[u] += dp[v];
}
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if(v == fa)
continue;
for (int j = 0; j < vec[v].size(); j++)
tree.rt[u] = tree.modify(1, tot, vec[v][j].pos, tree.rt[u], tree.rt[u], -vec[v][j].v);
}
int lim = b[u];
while(lim) {
pair<int, int> t = tree.get_top(1, tot, tree.rt[u]);
int d = min(lim, t.second);
dp[u] += d * ls[t.first];
tree.rt[u] = tree.modify(1, tot, t.first, tree.rt[u], tree.rt[u], -d);
lim -= d;
vec[u].push_back(opt{t.first, d});
}
for (int i = 0; i < vec[u].size(); i++)
tree.rt[u] = tree.modify(1, tot, vec[u][i].pos, tree.rt[u], tree.rt[u], vec[u][i].v);
if(u == 1) {
for (int i = 0; i < vec[u].size(); i++)
dp[u] -= ls[vec[u][i].pos] * vec[u][i].v;
}
dp[u] -= b[u] * ls[a[u]];
// if(u == 4)
// tree.out(1, tot, tree.rt[u]),
// cout << endl;
// write(dp[u]), putchar(' '), cout << u << " " << son << endl;
}
void redfs(int u, int fa, int rt) {
tree.ans = 0;
// cout << u << "adsf" << " " << ans << endl;
dp[u] = ans + tree.queryk(1, tot, rt, b[u]);
// tree.out(1, tot, rt);
// cout << endl;
__int128 org = ans;
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if(v == fa)
continue;
int trt = rt;
// cout << tree.tr[trt].cnt << endl;
for (int j = 0; j < vec[v].size(); j++)
trt = tree.modify(1, tot, vec[v][j].pos, trt, trt, vec[v][j].v), ans -= ls[vec[v][j].pos] * vec[v][j].v;
// cout << tree.tr[trt].cnt << endl;
tree.ans = 0;
trt = tree.queryp(1, tot, trt, tree.rt[v], trt, b[u]);
// cout << tree.ans << endl;
ans += tree.ans;
redfs(v, u, trt);
ans = org;
}
}
signed main() {
cin >> n >> op;
for (int i = 1; i <= n; i++)
cin >> a[i], ls[++tot] = a[i];
sort(ls + 1, ls + tot + 1);
tot = unique(ls + 1, ls + tot + 1) - ls - 1;
for (int i = 1; i <= n; i++)
a[i] = lower_bound(ls + 1, ls + tot + 1, a[i]) - ls;
for (int i = 1; i <= n; i++)
cin >> b[i];
for (int i = 1; i < n; i++) {
int x, y; cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1, 0);
ans = dp[1];
int rt = tree.rt[1];
for (int i = 0; i < vec[1].size(); i++)
rt = tree.modify(1, tot, vec[1][i].pos, rt, rt, vec[1][i].v);
redfs(1, 0, tree.rt[1]);
// for (int i = 1; i <= n; i++)
// cout << dp[i] << endl;
if(op == 1)
write(dp[1]), putchar('\n');
else {
for (int i = 1; i <= n; i++)
write(dp[i]), putchar('\n');
}
return 0;
}

浙公网安备 33010602011771号