专栏文章
[JOISC 2019] 做题记录
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mincmnzs
- 此快照首次捕获于
- 2025/12/02 00:13 3 个月前
- 此快照最后确认于
- 2025/12/02 00:13 3 个月前
P14340 [JOISC 2019] 考试 / Examination - 洛谷
每个人的信息 视为 ,直接套三维偏序,排序一维,归并一维,数据结构维护一维。
CPPconst int maxn = 1e5 + 5;
int n, m, ans[maxn], num[maxn << 1], cnt;
struct Node { int x, y, z, qid; } a[maxn << 1]; int tot;
int id[maxn << 1], tmp[maxn << 1], pt;
inline bool cmp(const int& i, const int& j) { return a[i].x > a[j].x; }
int val[maxn << 1];
inline void add(int x, int d) { for (; x <= cnt; x+= x & -x) val[x]+= d; }
inline int qry(int x) { int res = 0; for (; x; x-= x & -x) res+= val[x]; return res; }
inline void cdq(int l, int r)
{
if (l == r) return; int mid = l + r >> 1; cdq(l, mid); cdq(mid + 1, r);
int p1 = l, p2 = mid + 1; for (; p2 <= r; ++p2)
{
while (p1 <= mid && a[id[p1]].y >= a[id[p2]].y) { if (!a[id[p1]].qid) add(a[id[p1]].z, 1); ++p1; }
if (a[id[p2]].qid) ans[a[id[p2]].qid]+= qry(cnt) - qry(a[id[p2]].z - 1);
}
for (--p1; p1 >= l; --p1) if (!a[id[p1]].qid) add(a[id[p1]].z, -1);
p1 = l, p2 = mid + 1, pt = l;
while (p1 <= mid && p2 <= r) tmp[pt++] = a[id[p1]].y >= a[id[p2]].y ? id[p1++] : id[p2++];
while (p1 <= mid) tmp[pt++] = id[p1++];
while (p2 <= r) tmp[pt++] = id[p2++];
rep(i, l, r) id[i] = tmp[i];
}
int main()
{
read(n, m); int x, y, z; num[1] = 0; num[cnt = 2] = 2e9;
rep(i, 1, n) read(x, y), a[++tot] = {x, y, x + y, 0}, num[++cnt] = x + y;
rep(i, 1, m) read(x, y, z), a[++tot] = {x, y, z, i}, num[++cnt] = z;
sort(num + 1, num + cnt + 1); cnt = unique(num + 1, num + cnt + 1) - num;
rep(i, 1, tot) a[i].z = lower_bound(num + 1, num + cnt + 1, a[i].z) - num;
rep(i, 1, tot) id[i] = i;
sort(id + 1, id + tot + 1, [&](const int& i, const int& j)
{
if (a[i].x != a[j].x) return a[i].x > a[j].x;
return a[i].qid < a[j].qid;
});
cdq(1, tot);
rep(i, 1, m) write(ans[i]), pc(0xa);
return 0;
}
P14341 [JOISC 2019] 海狸的会面 / Meetings - 洛谷
发现 聚集的地点一定是 两两求 后深度最大的 。
设当前递归到的 为 ,在 的子树中随机一个点 ,并对 子树中的所有点 分别询问 ,以确定 是否在 的链上,或者在 的链上某个点的子树内。
对于链上的点可以通过一次询问比较两个点的深度,直接排序即可。
CPPmt19937 rnd((unsigned)time(NULL));
inline int rd(const int& l, const int& r) { return rnd() % (r - l + 1) + l; }
int Query(int u, int v, int w);
void Bridge(int u, int v);
inline void divide(int x, const vector<int>& node, const int& n)
{
if (node.size() == 1) { if (x < node[0]) Bridge(x, node[0]); else Bridge(node[0], x); return; }
vector<int> pos = node; shuffle(pos.begin(), pos.end(), rnd);
vector<int> id(n); for (int i = 0; i < (int)pos.size(); ++i) id[pos[i]] = i;
int y = pos[0]; vector<int> tmp; vector<vector<int>> son(pos.size() + 1);
for (int i = 1; i < (int)pos.size(); ++i)
{
int z = pos[i]; int lca = Query(x, y, z);
if (lca != z) { if (lca == x) son.back().pbk(z); else son[id[lca]].pbk(z); }
else tmp.pbk(z);
}
tmp.pbk(y);
for (int i = 0; i < (int)pos.size(); ++i) if (son[i].size()) divide(pos[i], son[i], n);
if (son.back().size()) divide(x, son.back(), n);
sort(tmp.begin(), tmp.end(), [=](const int& a, const int& b) { return Query(a, b, x) == a; });
for (int i = 0; i < (int)tmp.size(); ++i)
{
if (i == 0) { if (tmp[i] < x) Bridge(tmp[i], x); else Bridge(x, tmp[i]); }
else { if (tmp[i] < tmp[i - 1]) Bridge(tmp[i], tmp[i - 1]); else Bridge(tmp[i - 1], tmp[i]); }
}
}
void Solve(int n)
{
int rt = rd(0, n - 1);
vector<int> pos; for (int i = 0; i < n; ++i) if (i != rt) pos.pbk(i);
divide(rt, pos, n);
}
P14342 [JOISC 2019] 馕 / Naan - 洛谷
先求出每个人的 等分点。
从左到右分配馕属于谁。对于每个位置 ,从剩余的还未被分配的人中选择最小的 作为 ,并把 分给他。由于每次都这样分,所以必有 ,一定满足要求。
需要注意 计算的方法。
CPPconst int maxn = 2005; const ll inf = 1e18;
inline ll gcd(ll x, ll y) { if (x < y) swap(x, y); return y == 0 ? x : gcd(y, x % y); }
struct Div{ ll x, y; Div(ll _x, ll _y) : x(_x), y(_y) {} Div() : x(0), y(1) {} };
bool operator < (const Div& a, const Div& b) { return (ld)a.x / a.y < (ld)b.x / b.y; }
bool operator > (const Div& a, const Div& b) { return (ld)a.x / a.y > (ld)b.x / b.y; }
bool operator <= (const Div& a, const Div& b) { return (ld)a.x / a.y <= (ld)b.x / b.y; }
bool operator >= (const Div& a, const Div& b) { return (ld)a.x / a.y >= (ld)b.x / b.y; }
bool operator == (const Div& a, const Div& b) { return a.x == b.x && a.y == b.y; }
int n, l; ll v[maxn][maxn]; Div pos[maxn][maxn], ans1[maxn]; int ans2[maxn], vis[maxn];
int main()
{
read(n, l); rep(i, 1, n) rep(j, 1, l) read(v[i][j]);
rep(i, 1, n)
{
ll sum = 0; rep(j, 1, l) sum+= v[i][j];
Div ned = Div(sum, n);
int p = 1; ll cur = 0;
for (int j = 1; j <= n; ++j)
{
while (p < l && Div(cur + v[i][p], j) < ned) { cur+= v[i][p]; ++p; }
ll num = (ll)n * v[i][p] * (p - 1) + sum * j - cur * n;
ll den = (ll)n * v[i][p];
pos[i][j] = Div(num, den);
}
}
rep(i, 1, n)
{
int p = 0;
rep(j, 1, n) if (!vis[j] && (p == 0 || pos[p][i] > pos[j][i])) p = j;
ans1[i] = pos[p][i]; ans2[i] = p; vis[p] = 1;
}
rep(i, 1, n - 1) write(ans1[i].x), pc(' '), write(ans1[i].y), pc(0xa);
rep(i, 1, n) write(ans2[i]), pc(' ');
return 0;
}
P14343 [JOISC 2019] 两个天线 / Two Antennas - 洛谷
对于两个天线 ,若它们能够互相通信,则需要满足:
即
考虑将所有查询离线,并按照右端点 排序。从 到 扫描右端点 。对于每个 ,考虑所有左端点 ,计算它们与 的通信成本,即 。
考虑在扫描线时通过线段树维护信息。具体的,需要使它支持:
- 单点更新:激活/失效天线。
- 区间更新:用当前天线高度更新历史最大值。
- 区间查询:查询历史最大值。
容易使用吉司机线段树维护(好像比板子题还水?)。
CPPconst int maxn = 2e5 + 5, inf = 1e9;
int n, m, h[maxn], a[maxn], b[maxn], ans[maxn];
#define ls (p << 1)
#define rs (ls | 1)
struct Node{ int mnc, mxc, mxd, tgmn, tgmx; } t[maxn << 2];
inline void update(int p)
{
t[p].mnc = min(t[ls].mnc, t[rs].mnc);
t[p].mxc = max(t[ls].mxc, t[rs].mxc);
t[p].mxd = max(t[ls].mxd, t[rs].mxd);
}
inline void add(int p, int d)
{
if (t[p].mnc != inf) chkmx(t[p].mxd, -t[p].mnc + d);
if (t[p].mxc != -inf) chkmx(t[p].mxd, t[p].mxc - d);
chkmn(t[p].tgmn, d); chkmx(t[p].tgmx, d);
}
inline void pushdown(int p)
{
if (t[p].tgmn != inf) { add(ls, t[p].tgmn); add(rs, t[p].tgmn); t[p].tgmn = inf; }
if (t[p].tgmx != -inf) { add(ls, t[p].tgmx); add(rs, t[p].tgmx); t[p].tgmx = -inf; }
}
inline void build(int p, int l, int r)
{
t[p] = {inf, -inf, -inf, inf, -inf}; if (l == r) return;
int mid = l + r >> 1; build(ls, l, mid); build(rs, mid + 1, r); update(p);
}
inline void modify1(int p, int l, int r, int L, int R, int d)
{
if (l > R || r < L) return; if (l >= L && r <= R) return add(p, d), void();
int mid = l + r >> 1; pushdown(p); modify1(ls, l, mid, L, R, d); modify1(rs, mid + 1, r, L, R, d); update(p);
}
inline void modify2(int p, int l, int r, int x, int d)
{
if (l == r) { if (d == 0) { t[p].mnc = inf; t[p].mxc = -inf; } else t[p].mnc = t[p].mxc = d; return; }
int mid = l + r >> 1; pushdown(p); if (x <= mid) modify2(ls, l, mid, x, d); else modify2(rs, mid + 1, r, x, d); update(p);
}
inline int query(int p, int l, int r, int L, int R)
{
if (l > R || r < L) return -inf; if (l >= L && r <= R) return t[p].mxd;
int mid = l + r >> 1; pushdown(p); return max(query(ls, l, mid, L, R), query(rs, mid + 1, r, L, R));
}
vector<pii> pos[maxn], q[maxn];
int main()
{
read(n); rep(i, 1, n) read(h[i], a[i], b[i]);
rep(i, 1, n)
{
if (i + a[i] <= n) pos[i + a[i]].pbk({i, h[i]});
if (i + b[i] + 1 <= n) pos[i + b[i] + 1].pbk({i, 0});
}
read(m); rep(i, 1, m) { int l, r; read(l, r); q[r].pbk({l, i}); }
build(1, 1, n);
rep(j, 1, n)
{
for (auto [i, hi] : pos[j]) modify2(1, 1, n, i, hi);
if (j - a[j] >= 1) modify1(1, 1, n, max(1, j - b[j]), j - a[j], h[j]);
for (auto [l, id] : q[j]) ans[id] = query(1, 1, n, l, j);
}
rep(i, 1, m) write(ans[i] < 0 ? -1 : ans[i]), pc(0xa);
return 0;
}
P14344 [JOISC 2019] 两道料理 / Two Dishes - 洛谷
对于每一个 ,如果其计入贡献,则在 分钟内要完成 的第 步。完成第 步至少需要 的时间,从另一个角度说最多只有 的时间分给 。
考虑预处理:
将原题转化为从 走到 ,每次只能 或者 ,则
- 当 在路径上方或者在路径上时,有 的贡献( 完成第 步时, 完成不超过 步);
- 当 在路径下方或者在路径上时,有 的贡献( 完成第 步时, 完成不超过 步 )。
一个比较 的想法是先把所有 计入贡献,然后把 ,并把 ,于是可以把两类点都变为第二类计算。
令 表示从 走到 ,所有在路径上或者在路径下方的点的权值和的最大值。
记 表示第 列中所有纵坐标 的点的权值之和,则有转移:
相当于 并进行一次前缀 。
考虑维护 的差分,每个有值的 给 单点加。前缀取 相当于把负的 以及其后一段 的都变为 ,用线段树二分容易做到单 。
注意每个 上点的排序顺序。
CPPconst int maxn = 1e6 + 5; const ll inf = 1e15;
int n, m; ll ans = 0;
ll A[maxn], S[maxn], P[maxn];
ll B[maxn], T[maxn], Q[maxn];
vector<pil> vec[maxn];
#define ls (p << 1)
#define rs (ls | 1)
ll sum[maxn << 2], tag[maxn << 2];
inline void update(int p) { sum[p] = sum[ls] + sum[rs]; }
inline void pushdown(int p) { if (tag[p]) { sum[ls] = sum[rs] = 0, tag[ls] = tag[rs] = 1; tag[p] = 0; } }
inline void modify(int p, int l, int r, int x, ll d)
{
if (l == r) return sum[p]+= d, void();
int mid = l + r >> 1; pushdown(p); if (mid >= x) modify(ls, l, mid, x, d); else modify(rs, mid + 1, r, x, d); update(p);
}
inline void reset(int p, int l, int r, int L, int R)
{
if (l > R || r < L) return; if (l >= L && r <= R) { sum[p] = 0; tag[p] = 1; return; }
int mid = l + r >> 1; pushdown(p); reset(ls, l, mid, L, R); reset(rs, mid + 1, r, L, R); update(p);
}
inline ll query(int p, int l, int r, int L, int R)
{
if (l > R || r < L) return 0; if (l >= L && r <= R) return sum[p];
int mid = l + r >> 1; return query(ls, l, mid, L, R) + query(rs, mid + 1, r, L, R);
}
inline int binary(int p, int l, int r, int L, int R, ll pre, ll v)
{
if (l > R || r < L) return -1;
if (l >= L && r <= R) { if (pre + sum[p] < v) return -1; if (l == r) return l; }
int mid = l + r >> 1; pushdown(p);
int res = binary(ls, l, mid, L, R, pre, v);
if (~res) return res;
int L1 = max(L, l), R1 = min(R, mid);
ll lsum = 0;
if (L1 <= R1) lsum = query(ls, l, mid, L1, R1);
return binary(rs, mid + 1, r, L, R, pre + lsum, v);
}
int main()
{
read(n, m);
rep(i, 1, n) read(A[i], S[i], P[i]), A[i]+= A[i - 1];
rep(i, 1, m) read(B[i], T[i], Q[i]), B[i]+= B[i - 1];
rep(i, 1, n)
{
ll t = S[i] - A[i]; if (t < 0) continue; int l = 0, r = m, j = ~0;
while (l <= r) { int mid = l + r >> 1; if (B[mid] <= t) l = mid + 1, j = mid; else r = mid - 1; }
ans+= P[i]; if (j + 1 <= m) vec[i - 1].pbk({j + 1, -P[i]});
}
rep(i, 1, m)
{
ll t = T[i] - B[i]; if (t < 0) continue; int l = 0, r = n, j = ~0;
while (l <= r) { int mid = l + r >> 1; if (A[mid] <= t) l = mid + 1, j = mid; else r = mid - 1; }
if (~j) vec[j].pbk({i, Q[i]});
}
rep(i, 0, n - 1)
{
sort(vec[i].begin(), vec[i].end(), [](const pil& x, const pil& y)
{
return x.sec == y.sec ? x.fst < y.fst : x.sec > y.sec;
});
for (auto [j, w] : vec[i])
{
if (w >= 0) modify(1, 0, m, j, w);
else
{
int r = binary(1, 0, m, j, m, 0, -w);
// cerr << j << ' ' << r << endl;
if (r == -1) reset(1, 0, m, j, m);
else { modify(1, 0, m, r, w + query(1, 0, m, j, r - 1)); reset(1, 0, m, j, r - 1); }
}
}
}
ans+= sum[1]; for (auto [j, w] : vec[n]) ans+= w; write(ans); pc(0xa);
return 0;
}
P14345 [JOISC 2019] 两种交通 / Two Transportations - 洛谷
通信题,不会。
P14346 [JOISC 2019] 指定城市 / Designated Cities - 洛谷
转化一下题意:给定一颗 个点的树,每条边为双向边,两个方向权值不同。 组询问,每组询问给定 ,求任选 个点,把以其为根的内向树边权改为 后,整棵树的边权和的最小值。
考虑特殊性质。
设 。
时,对每个点 计算以它为根的内向树的边权和,即为 ,答案为 。容易换根 线性解决。
时,假设取了 作为选定点,那么答案为
其中 为树上两点简单路径的正反边权和,用类似求直径的二次扫描换根 可以线性解决。
特殊性质在 就结束了。猜测 时的情况满足 时选的点在 时也选。
考虑在 的基础上新增一个点,可以直接把 缩成一个点作为根,然后每次从结点中取价值最大的加入,并删去提供贡献的边的贡献。用线段树容易维护,复杂度 。
CPPconst int maxn = 2e5 + 5;
int n, q; struct E{ int to; ll w1, w2; }; vector<E> e[maxn];
ll tot, f[maxn], ans[maxn]; // f[u]:Suppose u is root.
namespace Case1
{
ll res;
inline void dfs1(int u, int fa) { f[u] = 0; for (auto o : e[u]) if (o.to != fa) { int v = o.to; ll w2 = o.w2; dfs1(v, u); f[u]+= f[v] + w2; } }
inline void dfs2(int u, int fa) { for (auto o : e[u]) if (o.to != fa) { int v = o.to; ll w1 = o.w1, w2 = o.w2; f[v] = f[u] - w2 + w1; dfs2(v, u); } }
void work() { dfs1(1, 0); dfs2(1, 0); res = 0; rep(i, 1, n) chkmx(res, f[i]); }
}
namespace Case2
{
int u1, u2; ll res, d[maxn];
inline void dfs(int u, int fa) { for (auto o : e[u]) if (o.to != fa) { int v = o.to; ll w1 = o.w1, w2 = o.w2; d[v] = d[u] + w1 + w2; dfs(v, u); } }
void work()
{
d[1] = 0; dfs(1, 0); u1 = 0; rep(i, 1, n) if (!u1 || f[u1] + d[u1] < f[i] + d[i]) u1 = i;
d[u1] = 0; dfs(u1, 0); u2 = 0; rep(i, 1, n) if (!u2 || (u1 != u2 && f[u2] + d[u2] < f[i] + d[i])) u2 = i;
res = (f[u1] + f[u2] + d[u2]) / 2;
}
}
namespace Case3
{
int u1, u2, vis[maxn], dfn[maxn], pos[maxn], siz[maxn], fat[maxn], tim = 0; ll d[maxn], ver[maxn], res;
inline void dfs1(int u, int fa) { if (u == u2) return vis[u] = 1, void(); for (auto o : e[u]) if (o.to != fa) { int v = o.to; dfs1(v, u); vis[u]|= vis[v]; } }
inline void dfs2(int u, int fa) { fat[u] = fa; siz[u] = 1; dfn[u] = ++tim; pos[tim] = u; for (auto o : e[u]) if (o.to != fa) { int v = o.to; dfs2(v, u); siz[u]+= siz[v]; } }
inline void dfs3(int u, int fa) { if (vis[u]) d[u] = 0; for (auto o : e[u]) if (o.to != fa) { int v = o.to; ll w1 = o.w1; d[v] = d[u] + w1; dfs3(v, u); if (!vis[v]) ver[v] = w1; else ver[v] = 0; } }
#define ls (p << 1)
#define rs (ls | 1)
struct Node{ ll val, tag; int nd; } t[maxn << 2];
Node operator + (const Node& x, const Node& y) { if (x.val > y.val) return {x.val, 0, x.nd}; else return {y.val, 0, y.nd}; }
inline void update(int p) { t[p] = t[ls] + t[rs]; }
inline void add(int p, ll d) { t[p].val-= d; t[p].tag+= d; }
inline void pushdown(int p) { if (t[p].tag) { add(ls, t[p].tag); add(rs, t[p].tag); t[p].tag = 0; } }
inline void build(int p, int l, int r) { if (l == r) return t[p] = {d[pos[l]], 0, pos[l]}, void(); int mid = l + r >> 1; build(ls, l, mid); build(rs, mid + 1, r); update(p); }
inline void modify(int p, int l, int r, int L, int R, ll d)
{
if (l > R || r < L) return; if (l >= L && r <= R) return add(p, d), void();
int mid = l + r >> 1; pushdown(p); modify(ls, l, mid, L, R, d); modify(rs, mid + 1, r, L, R, d); update(p);
}
void work()
{
mems(vis, 0); dfs1(u1, 0); dfs2(u1, 0); dfs3(u1, 0); build(1, 1, n);
res = ans[2];
for (int k = 3; k <= n; ++k)
{
if (res == 0) { ans[k] = 0; continue; }
Node o = t[1]; int u = o.nd; res-= o.val; ans[k] = res;
int x = u; while (x && !vis[x]) { modify(1, 1, n, dfn[x], dfn[x] + siz[x] - 1, ver[x]); vis[x] = 1; x = fat[x]; }
}
}
}
int main()
{
read(n); rep(i, 2, n) { int u, v; ll w1, w2; read(u, v, w1, w2); e[u].pbk({v, w1, w2}); e[v].pbk({u, w2, w1}); tot+= w1 + w2; }
Case1::work(); ans[1] = tot - Case1::res;
Case2::work(); ans[2] = tot - Case2::res;
Case3::u1 = Case2::u1; Case3::u2 = Case2::u2; Case3::work();
read(q); rep(i, 1, q) { int x; read(x); write(ans[x]); pc(0xa); }
return 0;
}
P14347 [JOISC 2019] 灯 / Lamps - 洛谷
每个位置不被两个赋值操作覆盖一定不劣。
如果只有翻转操作,则答案即为 的极长 段的个数。
考虑加了赋值操作后,每个位置的操作可以为:
-
不操作;
-
仅翻转;
-
使用一次赋值 并翻转;
-
使用一次赋值 并翻转。
因此定义 表示处理完第 个位置,是否使用了翻转操作,是否使用赋值 的操作,是否使用赋值 的操作,可以直接转移,复杂度线性。
(直接状压或许会好写一些?)
CPPconst int maxn = 1e6 + 5, inf = 0x3f3f3f3f;
int n, a[maxn], b[maxn], f[maxn][2][2][2], ans = inf;
int main()
{
read(n); char ch = gc();
while (!isdigit(ch)) ch = gc(); rep(i, 1, n) a[i] = ch ^ 0x30, ch = gc();
while (!isdigit(ch)) ch = gc(); rep(i, 1, n) b[i] = ch ^ 0x30, ch = gc();
mems(f, 0x3f); f[0][0][0][0] = 0;
rep(i, 1, n) rep(j, 0, 1) rep(k, 0, 1) rep(l, 0, 1) if (f[i - 1][j][k][l] < inf)
{
int d;
d = b[i] == 0; chkmn(f[i][d][1][0], f[i - 1][j][k][l] + (d && j == 0) + (k == 0));
d = b[i] == 1; chkmn(f[i][d][0][1], f[i - 1][j][k][l] + (d && j == 0) + (l == 0));
d = a[i] ^ b[i]; chkmn(f[i][d][0][0], f[i - 1][j][k][l] + (d && j == 0));
}
rep(j, 0, 1) rep(k, 0, 1) rep(l, 0, 1) chkmn(ans, f[n][j][k][l]);
write(ans); pc(0xa);
return 0;
}
P14348 [JOISC 2019] 穿越时空 Bitaro / Bitaro, who Leaps through Time - 洛谷
瞪了半天发现这不是树而是链。
考虑每次从 到 的最优策略,显然是直接从 向 走,如果走不了就等待或者用技能直到能走。
特判 。
不妨先令 ,那么令每条道路的 ,并相应地改变询问,那么约束就变为一条道路只能在时间 经过,且经过不需要花费。
一个显然的性质是如果一段区间 ,那么这条路的走法是固定的。
对于每个区间,记录一个四元组 , 记录路径是否唯一,如果唯一,那么从 进入, 出来的最小代价是 ;否则只要从 进入道路就无代价消耗,即 。
推一推两个路径的合并,发现有结合律,直接线段树维护,修改是容易的。
注意特判 的情况。
CPPconst int maxn = 3e5 + 5; const ll inf = 1e18;
int n, q; ll al[maxn], ar[maxn];
inline bool chk(ll xl, ll xr, ll yl, ll yr) { return max(xl, yl) <= min(xr, yr); }
inline ll calc(ll x, ll y) { return x > y ? x - y : 0; }
struct Node{ int o; ll il, ir, w; };
inline Node operator + (const Node& x, const Node& y)
{
Node z;
if (x.o && y.o) z = {1, x.il, y.ir, x.w + y.w + calc(x.ir, y.il)};
else if (x.o)
{
if (chk(x.ir, x.ir, y.il, y.ir)) z = {1, x.il, x.ir, x.w};
else if (x.ir < y.il) z = {1, x.il, y.il, x.w};
else z = {1, x.il, y.ir, x.w + (x.ir - y.ir)};
}
else if (y.o)
{
if (chk(x.il, x.ir, y.il, y.il)) z = {1, y.il, y.ir, y.w};
else if (x.ir < y.il) z = {1, x.ir, y.ir, y.w};
else z = {1, x.il, y.ir, y.w + (x.il - y.il)};
}
else
{
if (chk(x.il, x.ir, y.il, y.ir)) z = {0, max(x.il, y.il), min(x.ir, y.ir), 0};
else if (x.ir < y.il) z = {1, x.ir, y.il, 0};
else z = {1, x.il, y.ir, (x.il - y.ir)};
}
return z;
}
namespace Case1 // A < C, i->i+1
{
#define ls (p << 1)
#define rs (ls | 1)
Node t[maxn << 2];
inline void update(int p) { t[p] = t[ls] + t[rs]; }
inline void build(int p, int l, int r) { if (l == r) return t[p] = {0, al[l] - l, ar[l] - l - 1, 0}, void(); int mid = l + r >> 1; build(ls, l, mid); build(rs, mid + 1, r); update(p); }
inline void modify(int p, int l, int r, int x, ll dl, ll dr)
{
if (l == r) return t[p] = {0, dl, dr, 0}, void();
int mid = l + r >> 1; if (mid >= x) modify(ls, l, mid, x, dl, dr); else modify(rs, mid + 1, r, x, dl, dr); update(p);
}
inline Node query(int p, int l, int r, int L, int R)
{
if (l > R || r < L) return {0, -inf, inf, 0ll}; if (l >= L && r <= R) return t[p];
int mid = l + r >> 1; return query(ls, l, mid, L, R) + query(rs, mid + 1, r, L, R);
}
void init() { build(1, 1, n - 1); }
}
namespace Case2 // A > C, i+1->i
{
#define ls (p << 1)
#define rs (ls | 1)
Node t[maxn << 2];
inline void update(int p) { t[p] = t[rs] + t[ls]; }
inline void build(int p, int l, int r) { if (l == r) return t[p] = {0, al[l] - (n - l - 1), ar[l] - (n - l), 0}, void(); int mid = l + r >> 1; build(ls, l, mid); build(rs, mid + 1, r); update(p); }
inline void modify(int p, int l, int r, int x, ll dl, ll dr)
{
if (l == r) return t[p] = {0, dl, dr, 0}, void();
int mid = l + r >> 1; if (mid >= x) modify(ls, l, mid, x, dl, dr); else modify(rs, mid + 1, r, x, dl, dr); update(p);
}
inline Node query(int p, int l, int r, int L, int R)
{
if (l > R || r < L) return {0, -inf, inf, 0ll}; if (l >= L && r <= R) return t[p];
int mid = l + r >> 1; return query(rs, mid + 1, r, L, R) + query(ls, l, mid, L, R);
}
void init() { build(1, 1, n - 1); }
}
inline ll merge(const ll &x, const Node& y, const ll& z)
{
if (y.o == 0)
{
if (x > y.ir && z > y.ir) return x - y.ir;
if (x < y.il && z < y.il) return y.il - z;
return calc(x, z);
}
else
{
return y.w + calc(x, y.il) + calc(y.ir, z);
}
}
int main()
{
read(n, q); rep(i, 1, n - 1) read(al[i], ar[i]);
if (n > 1) Case1::init(), Case2::init();
rep(i, 1, q)
{
int opt; read(opt);
if (opt == 1)
{
int x; ll y, z; read(x, y, z);
Case1::modify(1, 1, n - 1, x, y - x, z - x - 1);
Case2::modify(1, 1, n - 1, x, y - (n - x - 1), z - (n - x));
}
else
{
int a, c; ll b, d; read(a, b, c, d);
if (a == c) { write(b > d ? b - d : 0); pc(0xa); }
else if (a < c)
{
b = b - a; d = d - c; Node tmp = Case1::query(1, 1, n - 1, a, c - 1);
ll res = merge(b, tmp, d); write(res); pc(0xa);
}
else
{
b = b - (n - a); d = d - (n - c); Node tmp = Case2::query(1, 1, n - 1, c, a - 1);
ll res = merge(b, tmp, d); write(res); pc(0xa);
}
}
}
return 0;
}
P14349 [JOISC 2019] 蛋糕 3 / Cake 3 - 洛谷
对于一组选定的 ,交换两个 的位置不会影响 ,只会影响 。
考虑 的 具有单调性。考虑先把 块蛋糕按 升序排序,记选中的序列为 ,最终答案为 ,有:
记 为强制选择 作为左端点, 作为右端点,在 中选 个 的最大价值,即 ,答案即为 。
考虑如果区间 是答案区间,那么 中的第 大的 应该小于 和 ,否则就能剔除 或 使极差减小,且 不降,更优。
因此修改 的定义,为选取 中前 大的 ,其位置集合 ,。
显然该式满足四边形不等式,,因为左右式 项抵消,分类讨论 和 与 中 的大小关系易得该式的 左式不大于右式。
因此,随着右端点 的增加,使答案最优的左端点 不会下降,直接分治即可,时间复杂度 。
CPPconst int maxn = 2e5 + 5; const ll inf = 1e18;
int n, m; struct Node{ ll v, c; } a[maxn];
ll ans = -inf, sum = 0;
multiset<ll> st1, st2; // used / re
int curl = 1, curr = 0;
inline void add(ll x)
{
st1.insert(x); sum+= x; if ((int)st1.size() <= m) return;
auto it = st1.begin(); sum-= *it; st2.insert(*it); st1.erase(it);
}
inline void del(ll x)
{
auto it = st1.find(x);
if (it != st1.end())
{
sum-= *it; st1.erase(it);
if (st2.size()) { auto it2 = st2.end(); --it2; sum+= *it2; st1.insert(*it2); st2.erase(it2); }
}
else
{
it = st2.find(x); if (it != st2.end()) st2.erase(it);
}
}
inline ll w(int l, int r)
{
while (curl > l) add(a[--curl].v);
while (curr < r) add(a[++curr].v);
while (curr > r) del(a[curr--].v);
while (curl < l) del(a[curl++].v);
if ((int)st1.size() < m) return -inf;
return sum - 2ll * (a[r].c - a[l].c);
}
inline void divide(int l, int r, int pl, int pr)
{
if (l > r || pl > pr) return;
int mid = l + r >> 1, p = pl; ll res = -inf;
for (int i = pl; i <= min(pr, mid); ++i)
{
ll cur = w(i, mid); if (cur > res) p = i, res = cur;
}
chkmx(ans, res);
divide(l, mid - 1, pl, p);
divide(mid + 1, r, p, pr);
}
int main()
{
read(n, m); rep(i, 1, n) read(a[i].v, a[i].c);
sort(a + 1, a + n + 1, [](const Node& x, const Node& y) { return x.c < y.c; });
divide(1, n, 1, n);
write(ans); pc(0xa);
return 0;
}
P14350 [JOISC 2019] 合并 / Mergers - 洛谷
把同一个组的城市缩成一个点,再跑 边双缩点。此时把树变得不可分即在新树上连边使得整张图边双连通。
最优操作显然是把度数为 的点两两连边,答案显然。
CPPconst int maxn = 5e5 + 5;
int n, K, lst[maxn];
struct E{ int to, nxt; };
E e1[maxn << 2]; int hd1[maxn], ct1 = 1; inline void add1(int u, int v) { e1[++ct1] = {v, hd1[u]}; hd1[u] = ct1; }
E e2[maxn << 1]; int hd2[maxn], ct2 = 1; inline void add2(int u, int v) { e2[++ct2] = {v, hd2[u]}; hd2[u] = ct2; }
int dfn[maxn], low[maxn], tim, bok[maxn << 2], pos[maxn], tot;
inline void tarjan(int u, int fa)
{
dfn[u] = low[u] = ++tim;
for (int o = hd1[u]; o; o = e1[o].nxt) if (e1[o].to != fa)
{
int v = e1[o].to;
if (!dfn[v])
{
tarjan(v, u); chkmn(low[u], low[v]);
// cerr << u << ' ' << v << ' ' << low[v] << ' ' << dfn[u] << endl;
if (low[v] > dfn[u]) bok[o] = bok[o ^ 1] = 1;
}
else chkmn(low[u], dfn[v]);
}
}
inline void dfs(int u)
{
pos[u] = tot;
for (int o = hd1[u]; o; o = e1[o].nxt) if (!bok[o]) { int v = e1[o].to; if (!pos[v]) dfs(v); }
}
int main()
{
read(n, K);
rep(i, 2, n) { int u, v; read(u, v); add1(u, v); add1(v, u); }
rep(i, 1, n) { int x; read(x); if (lst[x]) add1(i, lst[x]), add1(lst[x], i); lst[x] = i; }
tarjan(1, 0); rep(i, 1, n) if (!pos[i]) ++tot, dfs(i);
rep(u, 1, n) for (int o = hd1[u]; o; o = e1[o].nxt) { int v = e1[o].to; if (pos[u] != pos[v]) add2(pos[u], pos[v]); }
int ans = 0; rep(u, 1, tot) { int cnt = 0; for (int o = hd2[u]; o; o = e2[o].nxt) ++cnt; if (cnt == 1) ans++; }
write((ans + 1) / 2); pc(0xa);
return 0;
}
P14351 [JOISC 2019] 矿物 / Minerals - 洛谷
交互题。咕咕咕~
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...