从这里开始
- 题目请移至 loj 查看
每日憨批 ($infty / 1$)。感觉自己离滚蛋不远了。
Day 1
Problem A 一双木棋
dp 即可
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; typedef vector<int> vec; const int S = 1 << 20; const int inf = (~0u >> 2); int n, m; int encode(const vec& a) { int p = m, s = 0; for (int i = 0; i < n; i++) { while (p > a[i]) s = s << 1, p--; s = s << 1 | 1; } while (p) s <<= 1, p--; return s; } vec decode(int s) { vec rt; rt.reserve(n); int p = m; for (int i = n + m - 1; ~i; i--) { if ((s >> i) & 1) { rt.push_back(p); } else { p--; } } return rt; } int f[S]; bitset<S> vis; int A[2][12][12]; int dp(int s) { if (vis[s]) return f[s]; vis[s] = true; f[s] = -inf; vec v = decode(s); int sum = 0; for (auto x : v) sum += x; if (sum == n * m) return 0; int ls = m; for (int i = 0; i < n; i++) { if (v[i] ^ ls) { vec nv = v; nv[i]++; ls = v[i]; f[s] = max(f[s], A[sum & 1][i][v[i]] - dp(encode(nv))); } } return f[s]; } int main() { scanf("%d%d", &n, &m); for (int i = 0; i < 2; i++) { for (int x = 0; x < n; x++) { for (int y = 0; y < m; y++) { scanf("%d", A[i][x] + y); } } } int ans = dp(encode(vec(n, 0))); printf("%d ", ans); return 0; }
Problem B IIIDX
有相等的情形相当于由若干个要在某一个前缀中选出若干个的要求。这个可以用 Hall 定理判。它大概是后缀 min 大于等于 0。这个限制显然能用线段树维护。
Code
#include <algorithm> #include <iostream> #include <cstdlib> #include <cstdio> using namespace std; typedef bool boolean; const int N = 5e5 + 5; typedef class SegTreeNode { public: int mv, tg; SegTreeNode *l, *r; void pushUp() { mv = (l->mv < r->mv) ? (l->mv) : (r->mv); } void pushDown() { l->mv -= tg, l->tg += tg; r->mv -= tg, r->tg += tg; tg = 0; } }SegTreeNode; SegTreeNode pool[N << 2]; SegTreeNode *top = pool; SegTreeNode* newnode(int mv) { top->mv = mv, top->tg = 0; top->l = top->r = NULL; return top++; } typedef class SegTree { public: int n; SegTreeNode* rt; SegTree() { } SegTree(int n):n(n) { build(rt, 1, n); } void build(SegTreeNode*& p, int l, int r) { p = newnode(l); if (l == r) return ; int mid = (l + r) >> 1; build(p->l, l, mid); build(p->r, mid + 1, r); } void update(SegTreeNode* p, int l, int r, int ql, int qr, int val) { if (l == ql && r == qr) { p->mv -= val, p->tg += val; return; } if (p->tg) p->pushDown(); int mid = (l + r) >> 1; if (qr <= mid) update(p->l, l, mid, ql, qr, val); else if (ql > mid) update(p->r, mid + 1, r, ql, qr, val); else { update(p->l, l, mid, ql, mid, val); update(p->r, mid + 1, r, mid + 1, qr, val); } p->pushUp(); } int query(SegTreeNode* p, int l, int r, int len) { if (l == r) return (p->mv >= len) ? (l) : (l + 1); if (p->tg) p->pushDown(); int mid = (l + r) >> 1; if (p->r->mv >= len) return query(p->l, l, mid, len); return query(p->r, mid + 1, r, len); } int query(int len) { return query(rt, 1, n, len); } void update(int l, int r, int val) { update(rt, 1, n, l, r, val); } }SegTree; int n; double K; int fa[N], ds[N]; int siz[N], cnt[N], ans[N]; SegTree st; inline void init() { scanf("%d%lf", &n, &K); for (int i = 1; i <= n; i++) scanf("%d", ds + i); sort(ds + 1, ds + n + 1, greater<int>()); for (int i = 1; i <= n; i++) fa[i] = i / K; for (int i = n; i; i--) siz[fa[i]] += (++siz[i]); cnt[n] = 1; for (int i = n - 1; i; i--) cnt[i] = (ds[i] == ds[i + 1]) ? (cnt[i + 1] + 1) : (1); } inline void solve() { st = SegTree(n); for (int i = 1, r; i <= n; i++) { if (i > 1 && fa[i] != fa[i - 1]) st.update(ans[fa[i]], n, -siz[fa[i]] + 1); r = st.query(siz[i]); ans[i] = r + (--cnt[r]); st.update(ans[i], n, siz[i]); } for (int i = 1; i <= n; i++) printf("%d ", ds[ans[i]]); } int main() { init(); solve(); return 0; }
Problem C 秘密袭击
常规做法是枚举一个点,算它是第 $K$ 大的方案数。然后这个涉及到每次把一个点的重量从 0 变成 1。换根相当于是要求合并链上所有前缀背包的和或者后缀背包的和。这个可以把生成函数用点值表示,然后全局平衡二叉树瞎维护一下就行了。
标算做法大概是考虑计算大于等于 $v$ 中的点选了 $j$ 个,然后对于每个点来说是一个区间加物品。现在要求合并子节点信息,以及区间加物品。后者用线段树维护,前者可以线段树合并。实际还有很多细节要处理,此处省略各种细节一万字。
然而你笑道,正解无用,暴力把分送。
说了这么多,还是暴力好写。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 1670; const int Mod = 64123; #define ll long long void inc(ll& x) { if (x >= Mod) x -= Mod; } int n, K; ll f[N][N]; int a[N], id[N]; int sz[N], fa[N]; vector<int> G[N]; bitset<N> vis; void recalc(int p) { for (int i = 0; i <= K && i <= sz[p]; i++) f[p][i] = 0; f[p][0] = 1; int sum = 0; for (auto e : G[p]) { if (e ^ fa[p]) { for (int i = min(sum, K); ~i; i--) { if (!f[p][i]) continue; for (int j = min(K - i, sz[e]); ~j; j--) { f[p][i + j] += f[p][i] * f[e][j]; } } sum += sz[e]; for (int i = 0; i <= K && i <= sum; i++) f[p][i] %= Mod; } } if (vis.test(p)) { for (int i = min(K, sum + 1); i; i--) f[p][i] = f[p][i - 1]; f[p][0] = 0; } } void rev(int p) { if (!fa[p]) { return; } int q = fa[p]; rev(q); fa[q] = p; fa[p] = 0; sz[q] -= sz[p]; sz[p] += sz[q]; recalc(q); } void dfs(int p, int fa) { ::fa[p] = fa; for (auto e : G[p]) { if (e ^ fa) { dfs(e, p); } } recalc(p); } int main() { scanf("%d%d%*d", &n, &K); for (int i = 1; i <= n; i++) { scanf("%d", a + i); id[i] = i; } for (int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); } sort(id + 1, id + n + 1, [&] (int x, int y) { return a[x] > a[y]; }); vis.set(id[1]); sz[id[1]] = 1; dfs(id[1], 0); ll ans = f[id[1]][K] * a[id[1]]; for (int i = 2; i <= n; i++) { int p = id[i]; vis.set(p); rev(p); sz[p]++; recalc(p); ans += f[p][K] * a[p]; } printf("%lld ", ans % Mod); return 0; }
Day 2
Problem A 劈配
第一问不断加边暴力增广。
第二问相当于询问第 $i$ 个人只加入志愿在 $[1, s_i]$ 的边,在匈牙利树上到达左侧小于 $i$ 的点中的点权的最大值。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 205; int T, n, m; int cap[N], ans[N]; vector<int> mat[N]; vector<int> a[N][N]; vector<int> G[N]; boolean visL[N], visR[N]; boolean augment(int p) { if (visL[p]) return false; visL[p] = true; for (auto e : G[p]) { if (visR[e]) continue; visR[e] = true; if ((signed) mat[e].size() < cap[e]) { mat[e].push_back(p); return true; } for (auto& np : mat[e]) { if (augment(np)) { np = p; return true; } } } return false; } void solve() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { scanf("%d", cap + i); } cap[m + 1] = 233; for (int i = 1; i <= n; i++) { for (int j = 1, x; j <= m; j++) { scanf("%d", &x); if (x) a[i][x].push_back(j); } a[i][m + 1].push_back(m + 1); } for (int i = 1; i <= n; i++) { fill(visL + 1, visL + n + 1, false); fill(visR + 1, visR + m + 2, false); for (int j = 1; j <= m + 1; j++) { G[i] = a[i][j]; visL[i] = false; if (augment(i)) { ans[i] = j; break; } } } for (int i = 1; i <= n; i++) { printf("%d ", ans[i]); } putchar(' '); for (int i = 1; i <= n; i++) { G[i].clear(); } for (int i = 1; i <= m + 1; i++) { mat[i].clear(); } for (int i = 1, s; i <= n; i++) { scanf("%d", &s); int ans1 = 0; if (s >= ans[i]) { ans1 = i; } else { for (int j = 1; j <= s; j++) { for (auto e : a[i][j]) { G[i].push_back(e); } } fill(visL + 1, visL + n + 1, false); fill(visR + 1, visR + m + 1, false); augment(i); for (int j = 1; j < i; j++) { if (visL[j]) { ans1 = max(ans1, j); } } } printf("%d ", i - ans1); fill(visL + 1, visL + n + 1, false); fill(visR + 1, visR + m + 1, false); G[i] = a[i][ans[i]]; augment(i); } putchar(' '); for (int i = 1; i <= n; i++) { G[i].clear(); } for (int i = 1; i <= m + 1; i++) { mat[i].clear(); } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m + 1; j++) { a[i][j].clear(); } } } int main() { scanf("%d%*d", &T); while (T--) { solve(); } return 0; }
Problem B 林克卡特树
不难把问题转化成选 $K + 1$ 条点不相交的链,最大化边权和。
凸优化,dp 即可。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long #define pii pair<int, int> #define pli pair<ll, int> #define fi first #define sc second template <typename T> boolean vmax(T& a, T b) { return (a < b) ? (a = b, true) : false; } template <typename T> T smax(T a) { return a; } template <typename T, typename ...K> T smax(T a, const K& ...args) { return max(a, smax(args...)); } const int N = 3e5 + 5; const ll llf = 3e18; pli operator + (pli a, pli b) { return pli(a.first + b.first, a.second + b.second); } int n, K; vector<pii> G[N]; pli f[N][4]; void dfs(int p, int fa, ll mid) { static pli h[4]; pli *g = f[p]; g[0] = pli(0, 0); g[1] = pli(-llf, 0); g[2] = pli(-llf, 0); g[3] = pli(0, 0); for (auto _ : G[p]) { int e = _.first; int w = _.second; if (e == fa) continue; dfs(e, p, mid); pli *t = f[e]; h[0] = h[1] = h[2] = h[3] = pli(-llf, 0); pli v1 = max(smax(t[0], t[1], t[2]) + pli(mid, 1), t[3]); pli v2 = max(t[0], t[1]) + pli(w, 0); vmax(h[0], g[0] + v1); vmax(h[1], g[0] + v2); vmax(h[1], g[1] + v1); vmax(h[2], g[2] + v1); vmax(h[2], g[1] + v2); vmax(h[3], g[3] + v1); g[0] = h[0], g[1] = h[1]; g[2] = h[2], g[3] = h[3]; } } int main() { scanf("%d%d", &n, &K); ++K; for (int i = 1, u, v, w; i < n; i++) { scanf("%d%d%d", &u, &v, &w); G[u].emplace_back(v, w); G[v].emplace_back(u, w); } ll l = -3e11, r = 3e11, mid; while (l <= r) { mid = (l + r) >> 1; dfs(1, 0, mid); pli* t = f[1]; pli res = max(smax(t[0], t[1], t[2]) + pli(mid, 1), t[3]); if (res.sc < K) { l = mid + 1; } else { r = mid - 1; } } dfs(1, 0, l); pli* t = f[1]; pli res = max(smax(t[0], t[1], t[2]) + pli(l, 1), t[3]); ll ans = res.fi - K * l; printf("%lld ", ans); return 0; }
Problem C 制胡窜
发现很久没写线段树和后缀自动机,于是抱着写写板子的心态来写写这个题,于是又荒废了一个晚上。
一个没什么意思的讨论题。
假设你处理出了 right 集合。我们设三段字符串分别为 $s_1, s_2, s_3$
- $s_1$ 中包含 $s$
- $s_3$ 中包含 $s$
- $s_1, s_3$ 中同时包含 $s$
- $s_2$ 中包含 $s$,但 $s_1, s_3$ 都不包含
- $s$ 第一次出现没有被截断,最后一次出现也没有被截断
- $s$ 第一次出现被截断,最后一次出现没有被截断
- $s$ 第一次没有出现被截断,最后一次出现被截断
- $s$ 第一次出现和最后一次出现都被截断
最后一项要用 border 长度形成 log 个等差数列的性质吗?不不不,冷静一下,它只用维护相邻两项的差乘后一项的乘积,这个线段树瞎维护一下就行了。
实际还有很多细节要处理,此处省略各种细节一万字。
Code
/** * Copy the templates and you'll get AC. * Please stop writing problems! */ #include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 1e5 + 5; const int bzmax = 18; #define ll long long int n, m; typedef class Data { public: int mi, mx; ll sum; Data() { } Data(int x) : mi(x), mx(x), sum(0) { } Data(int mi, int mx, ll sum) : mi(mi), mx(mx), sum(sum) { } boolean empty() { return mi > mx; } Data operator + (Data b) { if (empty()) return b; if (b.empty()) return *this; return Data(mi, b.mx, sum + b.sum + (b.mi - mx) * 1ll * b.mi); } } Data; Data dnul (N, -1, 0); typedef class SegTreeNode { public: Data d; SegTreeNode *l, *r; SegTreeNode() : d(N, -1, 0) { } SegTreeNode(SegTreeNode* slf) : l(slf), r(slf) { } void push_up() { d = dnul; if (l) d = l->d; if (r) d = d + r->d; } } SegTreeNode; SegTreeNode pool[N * 40]; SegTreeNode *_top1 = pool; void insert(SegTreeNode* &p, int l, int r, int idx, const Data& v) { p = _top1++; if (l == r) { p->d = v; return; } int mid = (l + r) >> 1; if (idx <= mid) { insert(p->l, l, mid, idx, v); } else { insert(p->r, mid + 1, r, idx, v); } p->push_up(); } void insert(SegTreeNode*& p, int idx, const Data& v) { insert(p, 1, n, idx, v); } Data query(SegTreeNode* p, int l, int r, int ql, int qr) { if (!p) return dnul; if (l == ql && r == qr) return p->d; int mid = (l + r) >> 1; if (qr <= mid) { return query(p->l, l, mid, ql, qr); } else if (ql > mid) { return query(p->r, mid + 1, r, ql, qr); } return query(p->l, l, mid, ql, mid) + query(p->r, mid + 1, r, mid + 1, qr); } Data query(SegTreeNode* p, int l, int r) { return query(p, 1, n, l, r); } void merge(SegTreeNode* a, SegTreeNode* b, SegTreeNode* &p) { if (!a) { p = b; return; } if (!b) { p = a; return; } p = _top1++; merge(a->l, b->l, p->l); merge(a->r, b->r, p->r); p->push_up(); } inline int cti(char x) { return x - '0'; } typedef class TrieNode { public: int len; TrieNode* par; TrieNode* ch[10]; TrieNode* bz[bzmax]; SegTreeNode* rt; void init_bz() { if (!par) { bz[0] = this; } else { bz[0] = par; } for (int i = 1; i < bzmax; i++) bz[i] = bz[i - 1]->bz[i - 1]; } void insert(int x) { ::insert(rt, x, Data(x)); } TrieNode* jump(int nlen) { TrieNode* p = this; for (int i = bzmax - 1; ~i; i--) { if (p->bz[i]->len >= nlen) { p = p->bz[i]; } } return p; } } TrieNode; TrieNode pool1[N << 1]; TrieNode* _top = pool1; TrieNode* newnode(int len) { _top->len = len; return _top++; } typedef class SuffixAutomaton { public: TrieNode *rt, *lst; SuffixAutomaton() : rt(newnode(0)), lst(rt) { } TrieNode* extend(char _) { int c = cti(_); TrieNode* p = newnode(lst->len + 1); TrieNode* f = lst; while (f && !f->ch[c]) f->ch[c] = p, f = f->par; if (!f) { p->par = rt; } else { TrieNode* q = f->ch[c]; if (q->len == f->len + 1) { p->par = q; } else { TrieNode* nq = newnode(f->len + 1); memcpy(nq->ch, q->ch, sizeof(nq->ch)); nq->par = q->par, q->par = p->par = nq; while (f && f->ch[c] == q) f->ch[c] = nq, f = f->par; } } return lst = p; } vector<TrieNode*> order; void topusort() { vector<int> cnt (lst->len + 1, 0); for (TrieNode* p = pool1; p != _top; p++) cnt[p->len]++; for (int i = 1; i < (signed) cnt.size(); i++) cnt[i] += cnt[i - 1]; order.resize(_top - pool1); for (TrieNode* p = pool1; p != _top; p++) order[--cnt[p->len]] = p; } } SuffixAutomaton; char s[N]; TrieNode* nodes[N]; SuffixAutomaton sam; ll Cn2(int n) { if (n < 3) return 0; return 1ll * n * (n - 1) / 2 - (n - 1); } ll solve(SegTreeNode* st, int len) { assert(st); Data dall = st->d; int L = dall.mi, R = dall.mx; ll ansL = Cn2(n - (L + len - 1) + 1); ll ansR = Cn2(R); ll ansM00 = 1ll * (L - 1) * (n - (R + len - 1)); if (L == R) return ansL + ansR + ansM00; ll ansLR = Cn2(R - L - len + 2); ll ansM01 = 1ll * (L - 1) * (min(len - 1, R - L)); ll ansM10 = 1ll * (min(len - 1, R - L)) * (n - (R + len - 1)); ll ansM11 = 0; Data d = query(st, max(1, R - len + 1), n); int M = d.mi, Led = L + len - 1; if (M == L) { ansM11 = 1ll * (R - L) * R - d.sum; } else { int pr = query(st, L, M - 1).mx; if (pr < Led) { ansM11 = 1ll * (pr - L) * (len - 1) + 1ll * (Led - pr) * R; d = query(st, pr, Led - 1); ansM11 -= d.sum + 1ll * (Led - d.mx) * query(st, Led, R).mi; } else { ansM11 = 1ll * (len - 1) * (len - 1); } } return ansL + ansR - ansLR + ansM00 + ansM01 + ansM10 + ansM11; } ll solve(int l, int r) { TrieNode* p = nodes[l]->jump(r - l + 1); return solve(p->rt, r - l + 1); } int main() { scanf("%d%d", &n, &m); scanf("%s", s + 1); for (int i = n; i; i--) nodes[i] = sam.extend(s[i]); sam.topusort(); auto& V = sam.order; for (auto p : V) p->init_bz(); reverse(V.begin(), V.end()); for (int i = 1; i <= n; i++) nodes[i]->insert(i); for (auto p : V) { if (p->par) { merge(p->par->rt, p->rt, p->par->rt); } } while (m--) { int l, r; scanf("%d%d", &l, &r); printf("%lld ", solve(l, r)); } return 0; }