闲话 23.2.21
闲话
今天放的歌是 a 老师写的 攀登 - 逍遥散人
词写的挺好的
但是游戏区 up 主跨区做音乐确实比不过职业的(
我就不睿频了(
今天该推啥歌呢?
模拟赛(
好我又回来写模拟赛的题解了(
这套题背景是 nfls 众(?)人(
T1 玩水
你不过 T1 你过啥题啊
你不过题你打锤子啊
.jpg
我们称满足 \(a[n - 1, m] = a[n, m - 1]\) 的位置 \((n, m)\) 为好位置。
一张图有解,当且仅当存在一个好位置在另一个的严格左上方或相邻。然后直接判断即可。
T2 假人
场上看到 \((\max, +)\) 卷积首先想到的就是 minkowski 和,但是冲了个假结论后就弃了。
首先把值域平移到 \([0, 4]\) 上,然后考虑暴力 dp。
设 \(f(i, j)\) 为在前 \(i\) 组物品中选择 \(j\) 组时的最大权值和。可以证明,设 \(f_{i, k} (x) = f(i, 12x + k)\),则 \(\forall i, k,\ f_{i, k} (x)\) 是上凸的。
《证明》
首先有结论:取一个 \([0, 4]\cap \mathbb N\) 的子集 \(A\),若 \(\sum_{a\in A} a = 24\),则 \(\exists B\subset A, \sum_{b\in B} b = 12\)。
总觉得数学那边有一道联赛题和这个很像,但是 oi 这边可以直接搜出正确性。当然也可以分讨(
然后可以考虑直接转移了。考察 \(f_{i, k}\) 代入 \(x - 1, x, x + 1\) 的过程。
考察 \(j : 12x + k - 12 \to 12x + k + 12\) 的过程中的最优方案,设第 \(m\) 组选择数量增加 \(\Delta_m\),这里 \(\Delta_m\in [-4, 4]\),且 \(\sum \Delta = 24\)。我们总能通过贪心的方式将这些值合并,得到一系列 \([0, 4]\) 的值。根据如上的结论,我们能取得两组和为 \(12\) 的选择方法。将权值和最大的一组施在 \(j : 12x + k - 12 \to 12 x + k\) 的过程中,我们能得到
这自然引出了凸性。
说的道理
随后我们就可以通过分治求得每组的答案了,过程中通过 minkowski 和合并即可。
code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> inline T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> inline T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; for (register int CaseNo = 1; CaseNo <= T; ++ CaseNo)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 1e5 + 10, mod = 1e9 + 7;
int n, t1, sum, k[N];
struct poly : vector<ll> {
inline void redegree(int new_degree) {
resize(new_degree + 1);
}
inline int degree() const {
return (int)(size()) - 1;
}
inline friend poly operator* (poly f, poly g) {
if (f.empty() or g.empty()) return { };
poly ret;
adjacent_difference(f.begin(), f.end(), f.begin());
adjacent_difference(g.begin(), g.end(), g.begin());
ret.eb(f.front() + g.front());
merge(f.begin() + 1, f.end(), g.begin() + 1, g.end(), back_inserter(ret), greater<>());
partial_sum(ret.begin(), ret.end(), ret.begin());
return ret;
}
inline poly& operator *= (const poly& b) {
*this = (*this) * b;
return *this;
}
} p[N];
inline void update(poly& a, poly b, int offset) {
if (b.empty()) return;
if (a.size() < b.size() + offset)
a.resize(b.size() + offset);
for (int i = 0; i < b.size(); i++)
a[i + offset] = max(a[i + offset], b[i]);
}
array<poly, 12> solve(int l, int r) {
array<poly, 12> ans;
if (l == r) {
for (int i = 0; i < k[l]; i++) ans[i].push_back(p[l][i]);
return ans;
} int mid = (l + r) >> 1;
auto al = solve(l, mid), ar = solve(mid + 1, r);
for (int li = 0; li < 12; li++)
for (int ri = 0; ri < 12; ri++) update(ans[(li + ri) % 12], al[li] * ar[ri], li + ri >= 12);
return ans;
}
signed main() {
cin >> n;
rep(i,1,n) {
cin >> k[i]; sum += k[i] - 1;
p[i].redegree(k[i]);
rep(j,0,k[i] - 1) cin >> p[i][j];
} auto ret = solve(1, n);
rep(t,0,sum) cout << ret[t % 12][t / 12] << ' ';
}
T3 切题
切题排行榜
用户名 \(\qquad\qquad\)格言 \(\qquad\qquad\qquad\qquad\qquad\qquad\qquad\qquad\) 题数
1 superay \(\qquad\) 躺上切题榜 rk1 共计 ??? 天 $\qquad\qquad\quad \ \ \ $ 464
2 wzy \(\qquad\qquad\) <script>alert("1")</script> \(\qquad\qquad\quad\) 408
3 lqs2015 \(\qquad\) 前排观看切题榜 rk1, rk2 神仙打架 /se $\qquad\ $ 392
直接按最大流模型建图后跑,即可获得 10~40pts 不等的好成绩。
要说的结论其实就是 Gale-Ryser 定理,也就是需要满足 \(\forall k, \sum_{i =1}^k a_i \le \sum_{i = 1}^m \min(b_i, k)\),不再赘述了。然后考虑如何维护。
我们设 \(c_k = \sum_{i = 1}^m [b_i \ge k]\),则可以发现 \(\sum_{i = 1}^m \min(b_i, k) = \sum_{i = 1}^k c_k\)。接下来只需要考虑如何维护 \(\sum_{i = 1}^k (c_i - a_i) \ge 0\)。
这个可以直接维护了,但是我还是接着转化(
考虑取个反后加 \(\sum a_i\),也就是 \(\sum_{i = 1}^k c_i + \sum_{i = k + 1}^n a_i \ge \sum_{i = 1}^n a_i\)。
这个好维护了。我们在线段树上第 \(k\) 个叶子放上式左侧的值,最后只需要看最小值即可。初始化时考虑对每个 \(k\) 计算 \(\sum_{i = 1}^m \min(b_i, k) + \sum_{i = k + 1}^n a_i\)。后面的值可以前缀和优化,前面的值可以整个指针 \(i\) 维护 \(\le k\) 的最大的 \(b_i\),前半部分求和,后半部分贡献都是 \(i\)。
\(a, b\) 的加一减一也好维护了。粘个场上写的注释
- 找到 a[id] 对应最靠前的 a 的排名 假设是 k
[1 ~ k - 1] + 1 - 找到 a[id] 对应最靠后的 a 的排名 假设是 k
[1 ~ k - 1] - 1 - 找到 b[id] + 1 后的值 c 以及 c 对应的位置
[k ~ n] + 1 - 找到 b[id] - 1 后的值 c 以及 c 对应的位置
[k ~ n] - 1
然后就是 \(O((n + q)\log n)\) 了。
code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; for (register int CaseNo = 1; CaseNo <= T; ++ CaseNo)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 3e5 + 10, mod = 1e9 + 7;
int n, m, a[N], b[N], va[N], vb[N], typ, id;
ll sum, tsum, suma[N], sumb[N], wtf[N];
struct SegmentTree {
#define ls (p << 1)
#define rs (p << 1 | 1)
#define val(p) seg[p].val
#define lzy(p) seg[p].lzy
struct node {
ll val; int lzy;
} seg[N << 2];
void ps_d(int p) {
if (!lzy(p)) return ;
val(ls) += lzy(p), val(rs) += lzy(p);
lzy(ls) += lzy(p), lzy(rs) += lzy(p);
lzy(p) = 0;
}
void build(int p, int l, int r) {
if (l == r) {
val(p) = wtf[l];
return;
} int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
val(p) = min(val(ls), val(rs));
}
void update(int p, int l, int r, int L, int R, int v) {
if (L > R) return ;
if (L <= l and r <= R) {
val(p) += v, lzy(p) += v;
return;
} int mid = l + r >> 1; ps_d(p);
if (L <= mid) update(ls, l, mid, L, R, v);
if (mid < R) update(rs, mid + 1, r, L, R, v);
val(p) = min(val(ls), val(rs));
}
} Tr;
struct fenwick {
int Index[N], siz;
void add(int p, int v) {
siz += v; ++ p;
for (; p <= 750005; p += p & -p)
Index[p] += v;
}
int query(int p) {
int ret = 0; ++ p;
for (; p > 0; p ^= p & -p)
ret += Index[p];
return ret;
}
} fw;
signed main() {
cin >> n >> m;
rep(i,1,n) cin >> a[i], va[i] = a[i], sum += a[i], fw.add(a[i], 1);
rep(i,1,m) cin >> b[i], vb[i] = b[i];
sort(va + 1, va + 1 + n, greater<>());
sort(vb + 1, vb + 1 + m);
rep(i,1,max(n, m)) suma[i] = suma[i - 1] + va[i], sumb[i] = sumb[i - 1] + vb[i];
vb[m + 1] = 1e9;
wtf[0] = sum;
for (int i = 1, now = 0; i <= n; ++ i) {
while (vb[now + 1] <= i) ++ now;
wtf[i] = suma[n] - suma[i] + sumb[now] + i * (m - now);
}
Tr.build(1, 0, n);
multi {
cin >> typ >> id;
if (typ == 1) {
++ sum;
int id2 = n - fw.query(a[id]) + 1;
Tr.update(1, 0, n, 0, id2 - 1, 1);
fw.add(a[id], -1); fw.add(++ a[id], 1);
} else if (typ == 2) {
-- sum;
int id2 = n - fw.query(a[id] - 1);
Tr.update(1, 0, n, 0, id2 - 1, -1);
fw.add(a[id], -1); fw.add(-- a[id], 1);
} else if (typ == 3) Tr.update(1, 0, n, ++ b[id], n, 1);
else Tr.update(1, 0, n, b[id] --, n, -1);
cout << (Tr.seg[1].val >= sum) << '\n';
}
}
T4 天下第一
\(\color{black}{\text{d}}\color{red}{\text{jq_cpp}}\) 是天下第一的。
看到这句话我还以为中国象棋(
考虑一条链的条件:无环、点数 \(- 1 =\) 边数、度数 \(\le 2\)。
首先考虑对每个右端点求出满足无环的最左端点。这个可以双指针 + lct 简单(?)维护。
然后考虑对每个右端点求出满足度数 \(\le 2\) 的最左端点。这个可以双指针 + 桶简单维护。
然后我们现在有了一段极长的区间了,我们现在要的就是满足 点数 \(- 1 =\) 边数 的左端点数量,这个套路线段树。
由于无环,点数 \(- 1\ge\) 边数,因此我们需要的就是 点数 \(-\) 边数 的最小值出现次数,并若最小值为 \(1\) 则出现次数可以作为答案。线段树即可。
总时间复杂度 \(O((n + m)\log n)\)。
感觉正确性和 GERALD07 的证法差不多,而且做法也差不多。我正好前两天切了那题,然后 lct 是粘的。
代码不难写。
code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; for (register int CaseNo = 1; CaseNo <= T; ++ CaseNo)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 2e6 + 10, mod = 1e9 + 7;
int n, m, t1, t2;
ll ans;
vi e[N], f[N];
struct Link_Cut_Tree {
#define ls(p) spl[p].ch[0]
#define rs(p) spl[p].ch[1]
#define sn(p, s) spl[p].ch[s]
#define fa(p) spl[p].fa
#define fl(p) spl[p].fl
#define val(p) spl[p].val
#define mnv(p) spl[p].mnv
#define pos(p) spl[p].pos
#define notrt(p) ((p == ls(fa(p))) or (p == rs(fa(p))))
struct node {
int fa, ch[2], val, mnv, pos;
bool fl;
} spl[N << 2];
inline void flip(int p) { swap(ls(p), rs(p)), fl(p) ^= 1; }
inline void ps_p(int p) {
if (ls(p) and rs(p)) {
if (mnv(ls(p)) < mnv(rs(p))) mnv(p) = mnv(ls(p)), pos(p) = pos(ls(p));
else mnv(p) = mnv(rs(p)), pos(p) = pos(rs(p));
} else if (!ls(p) and !rs(p)) {
mnv(p) = 1e9 + 10;
} else if (!ls(p) and rs(p)) {
mnv(p) = mnv(rs(p)), pos(p) = pos(rs(p));
} else {
mnv(p) = mnv(ls(p)), pos(p) = pos(ls(p));
} if (val(p) < mnv(p)) mnv(p) = val(p), pos(p) = p;
}
inline void ps_d(int p) {
if (fl(p)) {
flip(ls(p)), flip(rs(p));
fl(p) = 0;
}
}
inline void rotate(int x) {
int y = fa(x), z = fa(y), k = rs(y) == x, son = sn(x, !k);
if (notrt(y)) sn(z, rs(z) == y) = x;
sn(x, !k) = y, sn(y, k) = son;
if (son) fa(son) = y;
fa(y) = x, fa(x) = z;
ps_p(y), ps_p(x);
}
void splay(int x) {
static int stk[N];
int z = 0, y = x;
stk[++ z] = x;
while (notrt(y)) stk[++ z] = y = fa(y);
while (z) ps_d(stk[z --]);
while (notrt(x)) {
y = fa(x), z = fa(y);
if (notrt(y)) rotate((ls(y) == x) ^ (ls(z) == y) ? x : y);
rotate(x);
} ps_p(x);
}
inline void access(int x) {
int y = 0;
while (x) {
splay(x); rs(x) = y; ps_p(x);
x = fa(y = x);
}
}
inline void makert(int x) {
access(x); splay(x); flip(x);
}
int findrt(int x) {
access(x), splay(x);
while (ls(x)) x = ls(x);
return splay(x), x;
}
inline void split(int u, int v) {
makert(u), access(v), splay(v);
}
inline void link(int u, int v) {
makert(u);
if (findrt(v) != u) fa(u) = v;
}
inline void cut(int u, int v) {
makert(u);
if (findrt(v) == u and fa(v) == u and ls(v) == 0) {
fa(v) = rs(u) = 0;
ps_p(u);
}
}
#undef ls
#undef rs
#undef sn
#undef fa
#undef fl
#undef val
#undef mnv
#undef pos
#undef notrt
} lct;
struct SegmentBeats {
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mnv(p) seg[p].mnv
#define cnt(p) seg[p].cnt
#define lzy(p) seg[p].lzy
struct node {
int mnv, cnt, lzy;
node(int m = 0, int c = 0, int l = 0) { mnv = m, cnt = c, lzy = l; }
inline friend node operator+ (const node& a, const node& b) {
node ret(min(a.mnv, b.mnv));
if (ret.mnv == a.mnv) ret.cnt += a.cnt;
if (ret.mnv == b.mnv) ret.cnt += b.cnt;
return ret;
}
} seg[N << 2];
void build(int p, int l, int r) {
if (l == r) return void(seg[p].cnt = 1);
int mid = l + r >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
seg[p] = seg[ls] + seg[rs];
}
inline void ps_d(int p) {
if (!lzy(p)) return;
mnv(ls) += lzy(p), lzy(ls) += lzy(p);
mnv(rs) += lzy(p), lzy(rs) += lzy(p);
lzy(p) = 0;
}
void update(int p, int l, int r, int L, int R, int v) {
if (L <= l and r <= R) {
mnv(p) += v, lzy(p) += v;
return;
} int mid = l + r >> 1; ps_d(p);
if (L <= mid) update(ls, l, mid, L, R, v);
if (mid < R) update(rs, mid + 1, r, L, R, v);
seg[p] = seg[ls] + seg[rs];
}
node query(int p, int l, int r, int L, int R) {
if (L <= l and r <= R) return seg[p];
int mid = l + r >> 1; ps_d(p);
if (R <= mid) return query(ls, l, mid, L, R);
else if (mid < L) return query(rs, mid + 1, r, L, R);
return query(ls, l, mid, L, R) + query(rs, mid + 1, r, L, R);
}
} Tr;
int buk[N], cnt;
inline void clear(int p) { buk[p] = 0; }
inline bool add(int p) {
++ buk[p];
if (buk[p] == 1) ++ cnt;
if (buk[p] == 2) -- cnt;
return buk[p] < 3;
}
inline void del(int p) {
for (auto v : f[p])
lct.cut(p, v), -- buk[p], -- buk[v];
}
signed main() {
cin >> n >> m;
rep(i,1,m) {
cin >> t1 >> t2;
if (t1 > t2) swap(t1, t2);
e[t2].eb(t1);
}
rep(i,1,n) sort(e[i].begin(), e[i].end(), greater<>());
int l = 1, r = 2;
Tr.update(1, 1, n, 1, 1, 1);
Tr.build(1, 1, n);
while (r <= n) {
Tr.update(1, 1, n, 1, r, 1);
while (!e[r].empty() && e[r].back() < l) e[r].pop_back();
while (l <= r && e[r].size() > 2) {
while (!e[r].empty() && e[r].back() <= l) e[r].pop_back();
del(l++);
}
for (auto v : e[r]) {
while (buk[v] > 1) del(l++);
while (lct.findrt(v) == lct.findrt(r)) del(l++);
if (v >= l) {
f[v].emplace_back(r);
++buk[v], ++buk[r];
lct.link(v, r);
Tr.update(1, 1, n, 1, v, -1);
}
}
auto res = Tr.query(1, 1, n, l, r);
if (res.mnv == 1) ans += res.cnt;
++r;
}
cout << ans + 1 << "\n";
}
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat230221.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。