20250916
T1
街机游戏
是最小链覆盖,Dilworth 变成最长反链,斜着扫描线即可。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++
char buf[1<<21], *p1, *p2, ch;
long long read() {
long long ret = 0, neg = 0; char c = getchar(); neg = (c == '-');
while (c < '0' || c > '9') c = getchar(), neg |= (c == '-');
while (c >= '0' && c <= '9') ret = ret * 10 + c - '0', c = getchar();
return ret * (neg ? -1 : 1);
}
int n, m;
int t[500005], a[500005];
pair<int, int> p[500005];
int d[500005], dcnt;
struct BIT {
int bit[500005];
void add(int x, int y) { for (++x; x; x -= lowbit(x)) bit[x] = max(bit[x], y); }
int query(int x) {
int ret = 0;
for (++x; x <= 500001; x += lowbit(x)) ret = max(ret, bit[x]);
return ret;
}
} bit;
int f[500005];
int main() {
freopen("arcade.in", "r", stdin);
freopen("arcade.out", "w", stdout);
m = read(), n = read();
for (int i = 1; i <= n; i++) t[i] = read();
for (int i = 1, x, y; i <= n; i++) a[i] = read(), x = a[i], y = t[i], p[i].first = x + y, p[i].second = y - x, d[i] = y - x;
sort(p + 1, p + n + 1);
sort(d + 1, d + n + 1), dcnt = unique(d + 1, d + n + 1) - d - 1;
int ans = 0;
for (int i = 1; i <= n; i++) {
int t = lower_bound(d + 1, d + dcnt + 1, p[i].second) - d;
ans = max(ans, f[i] = bit.query(t + 1) + 1);
bit.add(t, f[i]);
}
cout << ans << "\n";
return 0;
}
T2
01 完美序列
做前缀和,钦定下边界,只需要保证上边界不要爆掉。要钦定下边界 \(l\),就用不经过 \(l - 1\) 的方案减掉不经过 \(l\) 的方案。于是只需要求不经过 \(y = x + l\) 和 \(y = x + r\) 的 \((0, 0) \rightarrow (n, m)\) 路径数量。反射容斥即可。
代码
#include <iostream>
#include <string.h>
#define int long long
using namespace std;
const int P = 998244353;
inline void Madd(int &x, int y) { (x += y) >= P ? (x -= P) : 0; }
int qpow(int x, int y = P - 2) {
int ret = 1;
while (y) {
if (y & 1)
ret = ret * x % P;
y >>= 1;
x = x * x % P;
}
return ret;
}
int fac, ifac[50000005];
void Cpre(int n) {
fac = ifac[0] = ifac[1] = 1;
for (int i = 2; i <= n; i++) fac = 1ll * fac * i % P;
ifac[n] = qpow(fac);
for (int i = n - 1; i > 1; i--) ifac[i] = ifac[i + 1] * (i + 1ll) % P;
}
inline int C(int n, int m) { return (n < 0 || m < 0 || n < m) ? 0 : 1ll * fac * ifac[m] % P * ifac[n - m] % P; }
int n, m, K;
int ans;
int calc(int l, int r) {
if (l >= 0 || l >= m - n || r <= 0 || r <= m - n) return 0;
int k = r - l, ans = 0;
for (int t = -m / k; n - t * k + r >= 0; t++) {
ans += C(n + m, n - t * k);
if (ans >= P) ans -= P;
ans += P - C(n + m, n - t * k + r);
if (ans >= P) ans -= P;
}
return ans;
}
signed main() {
freopen("irori.in", "r", stdin);
freopen("irori.out", "w", stdout);
cin >> n >> m >> K;
Cpre(n + m);
for (int l = -K; l <= 0; l++) {
int r = l + K;
Madd(ans, (P + calc(l - 1, r + 1) - (l == 0 ? 0 : calc(l, r + 1))) % P);
}
cout << ans << "\n";
return 0;
}
T3
单身狗的复仇
视为二维点 \((i, p_i)\),由题目限制选择的点的 \(p_i\) 必须随 \(i\) 递增。一个点可以从上一个点转移来,当且仅当这两点构成的矩形中没有点。cdq 分治优化转移即可。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#define lowbit(x) ((x) & (-(x)))
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f;
int n;
int p[200005], w[200005];
int f[200005], stk[200005], sz;
struct Segment_Tree {
int mn[800005];
void Set(int o, int l, int r, int x, int y) {
if (l == r) return mn[o] = y, void();
int mid = (l + r) >> 1;
if (x <= mid) Set(o << 1, l, mid, x, y);
else Set(o << 1 | 1, mid + 1, r, x, y);
mn[o] = min(mn[o << 1], mn[o << 1 | 1]);
}
int Query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R) return mn[o];
int mid = (l + r) >> 1;
if (R <= mid) return Query(o << 1, l, mid, L, R);
if (L > mid) return Query(o << 1 | 1, mid + 1, r, L, R);
return min(Query(o << 1, l, mid, L, R), Query(o << 1 | 1, mid + 1, r, L, R));
}
} seg;
int o[200005];
struct BIT {
int bit[200005];
void Set(int x, int y) { for (; x <= n + 1; x += lowbit(x)) bit[x] = y; }
int query(int x) {
int ret = -1;
for (; x; x -= lowbit(x)) ret = max(ret, bit[x]);
return ret;
}
} bit;
int _f(int x) {
int l = 1, r = sz, mid, ret = sz + 1;
while (l <= r) {
mid = (l + r) >> 1;
if (p[stk[mid]] > x) ret = mid, r = mid - 1;
else l = mid + 1;
}
return ret;
}
void Solve(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
Solve(l, mid); sz = 0;
for (int i = l; i <= r; i++) o[i] = i;
sort(o + l, o + r + 1, [](int x, int y) { return p[x] < p[y]; });
int len = mid - l + 1;
for (int i = l; i <= r; i++) {
int x = o[i];
if (x > mid) {
int y = bit.query(x);
int pos = _f(y);
f[x] = min(f[x], (pos == sz + 1 ? inf : seg.Query(1, 1, len, pos, sz)) + w[x]);
bit.Set(x, p[x]);
} else {
while (sz && stk[sz] < x) --sz;
stk[++sz] = x; seg.Set(1, 1, len, sz, f[x]);
}
}
for (int i = mid + 1; i <= r; i++) bit.Set(i, -1);
Solve(mid + 1, r);
}
signed main() {
freopen("revenge.in", "r", stdin);
freopen("revenge.out", "w", stdout);
memset(bit.bit, -1, sizeof bit.bit);
memset(f, 63, sizeof f); f[0] = 0;
cin >> n;
for (int i = 1; i <= n; i++) cin >> p[i];
p[n + 1] = n + 1;
for (int i = 1; i <= n; i++) cin >> w[i];
Solve(0, n + 1);
cout << f[n + 1] << "\n";
return 0;
}
T4
毒性细菌
一个细菌可以放多次,则可以想到每个放 \(2^i\) 个。每次 \(8\) 个一组,可以确定这里面有没有 T。如果有 T,可以确定里面 S 的位置,并剩下一堆 R 和 T。否则需要再把这一组和一个 T 询问来找出里面所有的 S。对于剩下的那堆 R 和 T,由于在每一组里面我们已经通过询问确定了这里面存在 T,因此先把组内第一个 T 找到,利用掉这个信息,然后再把所有剩下的扔到一块并每次取 \(8\) 个出来找到里面第一个 T。找 T 可以二分。由于每次二分询问的东西很少,于是可以和前面的找 S 并行。然后就能过了。
代码
#include "toxic.h"
#include <iostream>
#include <algorithm>
#include <cassert>
#include <vector>
#include <random>
using namespace std;
random_device rd;
mt19937 mtrand(rd());
int o[305];
vector<pair<int, int> > v1;
vector<int> v2;
vector<int> Q;
int tox, c;
void init(int l, int r, int t = 0) {
Q.clear();
for (int i = l; i <= r; i++) for (int j = 0; j < (1 << (i - l)); j++) Q.emplace_back(o[i]);
if (t) Q.emplace_back(o[tox]);
}
char ans[315];
bool chk(int l, int r, vector<int> &V = v2) {
int t; Q.clear(); if (c != (int)v1.size()) init(v1[c].first, v1[c].second);
for (int j = l; j <= r; j++) Q.emplace_back(o[V[j]]);
if ((t = query_sample(Q)) == (int)Q.size()) {
for (int j = l; j <= r; j++) ans[o[V[j]]] = 'R';
return 0;
} else {
if (c != (int)v1.size()) { for (int j = 0; j < 8; j++) ans[o[v1[c].first + j]] = ((t & (1 << j)) ? 'S' : 'R'); ++c; }
return 1;
}
assert(0);
return 5148;
}
void determine_type(int n) {
Q.clear(); v1.clear(), v2.clear(), tox = 0, c = 0;
for (int i = 1; i <= n; i++) ans[i] = ' ', o[i] = i;
shuffle(o + 1, o + n + 1, mtrand);
for (int t, i = 1; i <= n; i += 8) {
init(i, min(n, i + 7));
if ((t = query_sample(Q)) != (int)Q.size()) {
vector<int> vt;
for (int j = 0; j < 8; j++) {
if (t & (1 << j)) ans[o[i + j]] = 'S';
else if (i + j <= n) vt.emplace_back(i + j);
}
int l = 0, r = (int)vt.size() - 1, mid;
while (l < r) {
mid = (l + r) >> 1;
if (chk(0, mid, vt)) r = mid;
else l = mid + 1;
}
for (int j = 0; j < l; j++) ans[o[vt[j]]] = 'R';
ans[o[tox = vt[l]]] = 'T';
for (int j = l + 1; j < (int)vt.size(); j++) v2.emplace_back(vt[j]);
}
else v1.emplace_back(i, min(n, i + 7));
}
int m = v2.size();
for (int i = 0, t; i < m; i++) {
Q.clear(); if (c != (int)v1.size()) init(v1[c].first, v1[c].second);
for (int j = i; j < min(m, i + 8); j++) Q.emplace_back(o[v2[j]]);
if ((t = query_sample(Q)) == (int)Q.size()) {
for (int j = i; j < min(m, i + 8); j++) ans[o[v2[j]]] = 'R';
i += 7;
} else {
if (c != (int)v1.size()) { for (int j = 0; j < 8; j++) ans[o[v1[c].first + j]] = ((t & (1 << j)) ? 'S' : 'R'); ++c; }
int l = 0, r = min(7, m - i - 1), mid, tmp = -1;
while (l < r) {
mid = (l + r) >> 1;
if (chk(i, i + mid)) r = mid;
else l = mid + 1;
}
assert(l != -1);
ans[o[tox = v2[i = i + l]]] = 'T';
}
}
if (c != (int)v1.size()) {
for (int i = c; i < (int)v1.size(); i++) {
Q.clear(); init(v1[i].first, v1[i].second, 1);
int t = query_sample(Q);
for (int j = 0; j < 8; j++) ans[o[v1[i].first + j]] = ((t & (1 << j)) ? 'S' : 'R');
}
}
for (int i = 1; i <= n; i++) answer_type(i, ans[i]);
}
反射容斥。
交互题,考虑如何利用之前的询问给出的信息。不能浪费。