退役(JSOI2026)前一周做题记录
20260224
Amereistr
显然的,我们如果知道 \(mask\) 这些钥匙都收集的最小时间,子集取 \(\min\) DP 一下即可。
发现从操作走到操作不太好搞,我们把操作挂在与“距离”关系更大的图的节点上。
对于每个节点,所有操作按照时间排序。
然后我们有状态 \((u,mask)\) 表示在 \(u\),当前收集了 \(mask\)。
我们有两种转移,同 \(mask\) 与最短路一样,由于是同一层的我们一起做掉;然后对于该层,我们可以在若干点收集钥匙并往上走,即,枚举每一个点上挂的操作,按照时间倒序枚举,确保操作的时间都在 \((u,mask)\) 之前;层间转移即可。
复杂度 \(O(n2^k\log n+m2^k)\)。
然而我赛时写了一个奇异搞笑做法。大样例跑了 7s,却赛后通过。不想补了,扔个假的在这吧。
// Note: Fake
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;
using i128 = __int128;
void File(string s) { freopen((s + ".in").c_str(), "r", stdin), freopen((s + ".out").c_str(), "w", stdout); }
template<typename T>
inline void chkmax(T &a, const T b) { if (a < b) a = b; }
template<typename T>
inline void chkmin(T &a, const T b) { if (a > b) a = b; }
inline void iofast() { cin.tie(0), cout.tie(0), ios::sync_with_stdio(0); }
const int inf = 0x3f3f3f3f, P = 998244353;
const int M = 20005;
vector<pair<int, int> > g[M];
struct Layer {
ll dis[M];
ll T;
priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > pq;
void init() {
memset(dis, 0x3f, sizeof(dis));
T = 0x3f3f3f3f3f3f3f3f;
}
Layer () {
init();
}
void upd(int p, ll t) {
if (dis[p] <= t) return ;
chkmin(T, t);
dis[p] = t;
pq.push({ t, p });
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dis[u]) continue;
for (auto [v, w] : g[u]) {
if (dis[v] > dis[u] + w) {
pq.push({ dis[v] = dis[u] + w, v });
}
}
}
}
} lay[1 << 10];
int n, m;
ll dp[1 << 10];
struct JMT {
int x, y, z;
};
void solve() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) g[i].clear();
for (int i = 1, a, b, w; i <= m; i++) {
scanf("%d%d%d", &a, &b, &w);
g[a].push_back({ b, w });
g[b].push_back({ a, w });
}
int k, q;
scanf("%d%d", &k, &q);
for (int i = 0; i < (1 << k); i++) lay[i].init();
lay[0].upd(1, 0);
vector<JMT> vec;
for (int i = 0; i < q; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
vec.push_back({ x, y, --z });
}
sort(vec.begin(), vec.end(), [&](JMT a, JMT b) { return a.x < b.x; });
for (int i = 0; i < q; i++) {
auto [x, y, z] = vec[i];
for (int j = 0; j < (1 << k); j++) if (!(j >> z & 1) && lay[j].dis[y] <= x) {
lay[j | (1 << z)].upd(y, x);
}
}
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i <= k; i++) {
for (int j = (1 << k) - 1; j; j--) {
for (int l = j; ; l = (l - 1) & j) {
chkmin(dp[j], max(dp[j ^ l], lay[l].T));
if (!l) break;
}
}
if (dp[(1 << k) - 1] != 0x3f3f3f3f3f3f3f3f) printf("%lld ", dp[(1 << k) - 1]);
else printf("-1 ");
}
puts("");
}
signed main() {
File("amereistr");
int t;
scanf("%d", &t);
while (t--) solve();
return 0;
}
World's end BLACKBOX
考虑如何计算 \(A\) 的权值,显然就是对于所有数 \(x\) 满足 \(\sum_{v\in A,v\le x}v=x-1\),\(\min x\) 为答案。
考虑观察它的性质:对于 \(\le x\) 的数组成的集合 \(S\),分为最大的数 \(v\) 和其余所有数,其余所有数的和必然 \(\ge v-1\),而不在 \(S\) 内最小的数必然 \(>(v-1)+v+1=2v\),即下一个数至少是 \(v\) 的两倍。
看到有一个“超过两倍”的关系,考虑以 \(2\) 为底倍增值域分块,如果 \(v\) 在一个块 \([2^{i-1},2^i)\) 中,则要求比 \(v\) 大的数中最小应 \(\ge 2^i\)。
考虑先枚举当前层(即 \(i\))然后枚举右端点,显然对于一个合法的左端点,要求存在 \([2^{i-1},2^i)\) 内的数,同时对于 \(\sum_{v<2^i}v<\min_{v\ge 2^i}v\)。第一个限制说明左端点有右界,第二个限制说明左端点有左界。因此可以尺取。
鉴于在定义中是“最小的 \(x\)”,所以当前的左端点区间必须不能包含上一层中,对于该右端点,左端点的区间(已经有更小的 \(x\) 了)。记录一下上次的区间即可。注意到,随着 \(i\) 的增加,对于固定右端点,左端点的合法右界是左移的,因此右界就是上一次的左界 \(-1\)(一个简单的原因,就是区间越长答案显然不减,因此如果答案变大了区间一定边长)。
快速计算区间答案可以用前缀和、二次前缀和,较为容易。
需要注意的是,前缀和不能取模,因为设计比较;而二次前缀和需要取模,否则会爆 long long。
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;
using i128 = __int128;
void File(string s) { freopen((s + ".in").c_str(), "r", stdin), freopen((s + ".out").c_str(), "w", stdout); }
template<typename T>
inline void chkmax(T &a, const T b) { if (a < b) a = b; }
template<typename T>
inline void chkmin(T &a, const T b) { if (a > b) a = b; }
inline void iofast() { cin.tie(0), cout.tie(0), ios::sync_with_stdio(0); }
const int inf = 0x3f3f3f3f, P = 998244353;
const int M = 1000006;
int n;
int a[M], R[M];
ll w[M];
ll s[M], ss[M];
int que[M];
signed main() {
File("blackbox");
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", a + i), R[i] = i;
ll ans = 0;
for (int i = 1; i <= 30; i++) {
for (int j = 1; j <= n; j++) {
s[j] = s[j - 1];
if (a[j] >= (1 << i)) w[j] = a[j];
else w[j] = 1e18, s[j] += a[j];
ss[j] = ss[j - 1] + s[j] % P;
}
int L = 1, ql = 1, qr = 0;
for (int j = 1; j <= n; j++) {
while (ql <= qr && w[j] <= w[que[qr]]) qr--;
que[++qr] = j;
while (L <= j && w[que[ql]] <= s[j] - s[L - 1] + 1) {
L++;
while (ql <= qr && que[ql] < L) ql++;
}
if (R[j] >= L) {
ans += (s[j] + 1) % P * (R[j] - L + 1) % P;
ans += P - (ss[R[j] - 1] - ss[L - 2]) % P;
R[j] = L - 1;
}
}
}
printf("%lld\n", ans % P);
return 0;
}
20260224
彩灯覆盖
考虑按题意建树,变成任意祖先链最多选 \(k\) 个,问最多权值和。
对于每一颗树单独考虑。
如果采用我最开始想到的 DP:\(dp_{u,k}\) 表示 \(u\) 这个点子树满足 \(k\) 的条件的时候,最大权值。此时的两个更新中,第二种 \(dp_{u,k}\gets \sum_{(u,v)}dp_{v,k-1}+a_u\) 是难以做到的。
考虑一种贪心的决策:对于节点 \(u\),如果在第一个子树内选择了 \(k\) 层,则其余均可选 \(k\) 层。那么对于链的情况,选择 \(k\) 层可以维护一个有序数列,从大到小排序,\(k\) 的答案即为前 \(k\) 项的和。扩展到这里,如果把两个子树合并,即把这两个子树的数列前若干项依次相加,即,如果在 \(v_1\) 子树内选择了第 \(k\) 小的点,则 \(v_2\) 也一定可以选一个第 \(k\) 小的点。
于是类启发式合并即可完成;注意到该数列长度为 \(O(dep)\) 级别的,长剖一下就可以了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void File(string s) {
freopen((s + ".in").c_str(), "r", stdin);
freopen((s + ".out").c_str(), "w", stdout);
}
const int M = 300005;
int n, m;
struct Itv {
int l, r, v;
friend bool operator < (Itv a, Itv b) {
return a.l != b.l ? a.l < b.l : a.r > b.r;
}
} a[M];
bool cov(Itv a, Itv b) {
return a.l <= b.l && a.r >= b.r;
}
void chkmax(int &A, int B) { if (A < B) A = B; }
void chkmin(int &A, int B) { if (A > B) A = B; }
int stk[M], tp;
int fa[M];
vector<int> g[M];
int val[M];
int dep[M];
int depth[M];
int son[M];
void dfs(int u) {
dep[u] = dep[fa[u]] + 1;
for (auto v : g[u]) {
dfs(v);
if (depth[v] > depth[u]) {
depth[u] = depth[v];
son[u] = v;
}
}
depth[u]++;
}
ll sum;
void dfs3(int u, priority_queue<ll> &pq) {
if (son[u]) dfs3(son[u], pq);
for (auto v : g[u]) if (v != son[u]) {
priority_queue<ll> p;
dfs3(v, p);
vector<ll> ad;
while (!p.empty()) {
ll x = p.top() + pq.top();
p.pop(), pq.pop();
ad.push_back(x);
}
for (auto v : ad) pq.push(v);
}
pq.push(val[u]);
}
ll ans1[M], ans2[M];
void work(int rt) {
dfs(rt);
priority_queue<ll> pq;
dfs3(rt, pq);
ll s = 0;
int sz = pq.size();
for (int i = 1; !pq.empty(); i++) {
ll v = pq.top();
pq.pop();
ans1[i] += (s += v);
}
ans2[sz + 1] += s;
}
signed main() {
File("cover");
scanf("%d%d", &n, &m);
n = m;
int tot = 0;
for (int i = 1; i <= m; i++) {
tot++;
scanf("%d%d%d", &a[tot].l, &a[tot].r, &a[tot].v);
a[tot].r--;
}
m = tot;
sort(a + 1, a + m + 1);
for (int i = 1; i <= m; i++) {
while (tp && !cov(a[stk[tp]], a[i])) {
tp--;
if (tp == 0) work(stk[tp + 1]);
}
if (tp) g[stk[tp]].push_back(i), fa[i] = stk[tp];
stk[++tp] = i;
val[i] = a[i].v;
}
work(stk[1]);
for (int i = 1; i <= n; i++) ans2[i] += ans2[i - 1];
for (int i = 1; i <= n; i++) printf("%lld ", ans1[i] + ans2[i] + sum);
return 0;
}
计数题
考虑合并的实质可以看做删除:删除相邻的 01 或者对于连续三个相同的数删掉两个。
因此一定保留的是原串的子序列。考虑我们作一个子序列自动机——不过这里的子序列指的是,中间跳过的部分都能被删干净的子序列,即答案序列。
正确性问题,即,如果有 \(i\to j_1,i\to j_2,j_1<j_2,a_{j_1}=a_{j_2}\),\(j_1,j_2\) 奇偶性相同。要求如果 \(i\to j_1\) 则须有 \(i\to j_2\),此时我们将 \([j_1,j_2-1]\) 合并为一个数 \(c\),然后把 \(s_{j_1},c,s_{j_2}\) 合并即可,都可以起到删除 \(s_{j-1},c\) 的效果。
考虑怎么构建自动机:
显然 \(i\) 可以连向 \(i+1\),如果 \(a_i=a_{i+1}\),那么只需要找下一个与 \(i\) 奇偶性不同的 \(j\) 使得 \(a_j\neq a_i\) 即可。
如果 \(a_i\neq a_{i+1}\),则后面跟奇数个不一样的(先缩为 1 个),再跟 2 个一样的即可。因此需要找到第一个奇偶性与 \(i\) 不同的 \(j\),满足 \(a_{j-1}=a_j=a_i\)。
一个需要注意的点,判断一个位置 \(i\) 是否能直接作为结尾,首先是奇偶性,其次是它的第二种情况,即后面存在不同奇偶性的 \(j\) 满足 \(a_i=a_{j-1}=a_j\),或者是 \(a_i=a_n\)。这个需要手写判断。
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;
using i128 = __int128;
void File(string s) { freopen((s + ".in").c_str(), "r", stdin), freopen((s + ".out").c_str(), "w", stdout); }
template<typename T>
inline void chkmax(T &a, const T b) { if (a < b) a = b; }
template<typename T>
inline void chkmin(T &a, const T b) { if (a > b) a = b; }
inline void iofast() { cin.tie(0), cout.tie(0), ios::sync_with_stdio(0); }
const int inf = 0x3f3f3f3f, P = 998244353;
const int M = 5000006;
int n;
char s[M];
int a[M];
int rec[2][M];
int two[2][M];
int calc_same(int p, int c) {
return two[c][p + 1];
}
int to[M][2];
int dp[M];
void madd(int &a, int b) {
if ((a += b) >= P) a -= P;
}
void solve() {
scanf("%s", s + 1);
n = strlen(s + 1);
for (int i = 1; i <= n; i++) a[i] = s[i] - '0';
rec[0][n + 2] = rec[1][n + 2] = n + 2;
rec[0][n + 1] = rec[1][n + 1] = n + 1;
for (int i = n; i; i--) {
for (int j : { 0, 1 }) rec[j][i] = rec[j][i + 2];
rec[a[i]][i] = i;
}
memset(dp, 0, sizeof(dp));
dp[1] = 1;
for (int i = 3; i <= n; i += 2) if (a[i] != a[1] && a[i] == a[i - 1]) { dp[i] = 1; break; }
two[0][n + 2] = two[1][n + 2] = n + 2;
two[0][n + 1] = two[1][n + 1] = n + 1;
for (int i = n; i; i--) {
for (int j : { 0, 1 }) two[j][i] = two[j][i + 2];
if (i > 1 && a[i] == a[i - 1]) two[a[i]][i] = i;
}
for (int i = 1; i <= n; i++) to[i][a[i + 1]] = i + 1;
for (int i = 1; i < n; i++) {
if (a[i] == a[i + 1]) to[i][!a[i + 1]] = rec[!a[i + 1]][i + 1];
else to[i][!a[i + 1]] = calc_same(i, !a[i + 1]);
}
for (int i = 1; i <= n; i++) {
if (to[i][0] <= n) madd(dp[to[i][0]], dp[i]);
if (to[i][1] <= n) madd(dp[to[i][1]], dp[i]);
if (i && !(n - i & 1) && (a[i] == a[n] || two[a[i]][i + 1] <= n)) madd(dp[n + 1], dp[i]);
}
printf("%d\n", dp[n + 1]);
}
signed main() {
File("count");
int t;
scanf("%d", &t);
while (t--) solve();
return 0;
}
20260225
激光炮
首先,把让 \(n\ge m\),使得方便对列状压 DP;每一列三个状态:全都被覆盖,有空缺,和有激光炮。考虑枚举一行的状态转移,发现复杂度太高;我们发现我们只关心一段内是否存在行间激光炮,而不关心其具体位置,因此可以一列一列的枚举,再记录一个行状态:都覆盖,有空缺和有激光炮。转移分讨即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void File(string s) { freopen((s + ".in").c_str(), "r", stdin); freopen((s + ".out").c_str(), "w", stdout); }
const int N = 12, M = 155, inf = 0x3f3f3f3f;
int dp[3][540000], F[3][540000];
void chkmin(int &a, int b) { if (a > b) a = b; }
int pw[N + 2];
inline int kbit(int v, int k) {
return (v / pw[k]) % 3;
}
int n, m;
char tmps[M][M], s[M][N];
void solve() {
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0;
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) scanf("%s", tmps[i]);
if (n < m) {
for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) s[j][i] = tmps[i][j];
swap(n, m);
}
else for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) s[i][j] = tmps[i][j];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
memset(F, 0x3f, sizeof(F));
if (s[i][j] != '#') {
for (int cur : { 0, 1, 2 }) {
for (int msk = 0; msk < pw[m]; msk++) {
// don't put
if (kbit(msk, j) == 2) {
// already exist, 2
chkmin(F[cur][msk], dp[cur][msk]);
}
else if (cur) {
// line existed / will exist, don't change
chkmin(F[cur][msk], dp[cur][msk]);
}
else {
// need
// msk = 1, cur = 0
chkmin(F[0][msk - kbit(msk, j) * pw[j] + pw[j]], dp[cur][msk]);
// cur = 1
chkmin(F[1][msk], dp[cur][msk]);
}
// put, 2, cur = 2
chkmin(F[2][msk - kbit(msk, j) * pw[j] + 2 * pw[j]], dp[cur][msk] + 1);
}
}
}
if (s[i][j] != '.') {
for (int cur : { 0, 1, 2 }) {
for (int msk = 0; msk < pw[m]; msk++) {
if (kbit(msk, j) != 1 && cur != 1) {
chkmin(F[0][msk - kbit(msk, j) * pw[j]], dp[cur][msk]);
}
}
}
}
memcpy(dp, F, sizeof(dp));
}
for (int cur : { 0, 1, 2 }) {
for (int msk = 0; msk < pw[m]; msk++) {
if (cur == 1) dp[cur][msk] = inf;
if (cur == 2) chkmin(dp[0][msk], dp[cur][msk]), dp[cur][msk] = inf;
}
}
}
int ans = inf;
for (int msk = 0; msk < pw[m]; msk++) {
bool f = false;
for (int j = 0; j < m; j++) if (kbit(msk, j) == 1) { f = true; break; }
if (!f) chkmin(ans, dp[0][msk]);
}
printf("%d\n", ans);
}
signed main() {
File("laser");
pw[0] = 1;
for (int i = 1; i <= N; i++) pw[i] = pw[i - 1] * 3;
int c, t;
scanf("%d%d", &c, &t);
while (t--) solve();
return 0;
}
最长单调子串问题
我们直接猜结论:一个数只可能被改成 \(-\inf\) or \(\inf\)。
此时我们对于六种状态:当前升 or 降,最后一位 \(-\inf\) or \(a_i\) or \(\inf\),贪心的保留操作次数最小的即可,想通则末尾连续段最短。
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;
using i128 = __int128;
void File(string s) { freopen((s + ".in").c_str(), "r", stdin), freopen((s + ".out").c_str(), "w", stdout); }
template<typename T>
inline void chkmax(T &a, const T b) { if (a < b) a = b; }
template<typename T>
inline void chkmin(T &a, const T b) { if (a > b) a = b; }
inline void iofast() { cin.tie(0), cout.tie(0), ios::sync_with_stdio(0); }
const int inf = 0x3f3f3f3f, P = 998244353;
const int M = 400005;
int n, m;
struct Node {
int opt, l1, l2, k;
Node (int o = inf, int l = inf, int L = inf, int K = 0) { opt = o, l1 = l, l2 = L, k = K; }
Node upd(int v1, int v2) {
Node res = *this;
if ((v2 - v1) * k >= 0) {
res.l1++;
if (v1 == v2) res.l2++; else res.l2 = 1;
if (abs(v2) >= inf) res.opt++;
}
else {
res.k = -res.k;
res.l1 = res.l2 + 1;
res.l2 = 1;
if (abs(v2) >= inf) res.opt++;
}
return res;
}
void chk(Node a) {
if (a.l1 >= m) return ;
if (a.opt < opt) *this = a;
else if (a.opt == opt && a.l1 < l1) *this = a;
else if (a.opt == opt && a.l1 == l1 && a.l2 < l2) *this = a;
}
};
Node dp[M][2][3];
int a[M];
void solve() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", a + i);
dp[1][0][0] = { 1, 1, 1, -1 };
dp[1][0][1] = { 0, 1, 1, -1 };
dp[1][0][2] = { 1, 1, 1, -1 };
dp[1][1][0] = { 1, 1, 1, 1 };
dp[1][1][1] = { 0, 1, 1, 1 };
dp[1][1][2] = { 1, 1, 1, 1 };
for (int i = 2; i <= n; i++) {
for (auto o : { 0, 1 }) {
for (auto O : { 0, 1, 2 }) {
dp[i][o][O] = {};
}
}
dp[i][0][0].chk(dp[i - 1][0][0].upd(-inf, -inf - 1));
dp[i][0][0].chk(dp[i - 1][1][0].upd(-inf, -inf - 1));
dp[i][1][0].chk(dp[i - 1][0][0].upd(-inf - 1, -inf));
dp[i][1][0].chk(dp[i - 1][1][0].upd(-inf - 1, -inf));
dp[i][0][0].chk(dp[i - 1][0][1].upd(a[i - 1], -inf));
dp[i][0][0].chk(dp[i - 1][1][1].upd(a[i - 1], -inf));
dp[i][0][0].chk(dp[i - 1][0][2].upd(inf, -inf));
dp[i][0][0].chk(dp[i - 1][1][2].upd(inf, -inf));
dp[i][1][2].chk(dp[i - 1][0][0].upd(-inf, inf));
dp[i][1][2].chk(dp[i - 1][1][0].upd(-inf, inf));
dp[i][1][2].chk(dp[i - 1][0][1].upd(a[i - 1], inf));
dp[i][1][2].chk(dp[i - 1][1][1].upd(a[i - 1], inf));
dp[i][1][2].chk(dp[i - 1][0][2].upd(inf, inf + 1));
dp[i][1][2].chk(dp[i - 1][1][2].upd(inf, inf + 1));
dp[i][0][2].chk(dp[i - 1][0][2].upd(inf + 1, inf));
dp[i][0][2].chk(dp[i - 1][1][2].upd(inf + 1, inf));
dp[i][1][1].chk(dp[i - 1][0][0].upd(-inf, a[i]));
dp[i][1][1].chk(dp[i - 1][1][0].upd(-inf, a[i]));
dp[i][0][1].chk(dp[i - 1][0][2].upd(inf, a[i]));
dp[i][0][1].chk(dp[i - 1][1][2].upd(inf, a[i]));
if (a[i] > a[i - 1])
dp[i][1][1].chk(dp[i - 1][0][1].upd(a[i - 1], a[i])), dp[i][1][1].chk(dp[i - 1][1][1].upd(a[i - 1], a[i]));
else if (a[i] < a[i - 1])
dp[i][0][1].chk(dp[i - 1][0][1].upd(a[i - 1], a[i])), dp[i][0][1].chk(dp[i - 1][1][1].upd(a[i - 1], a[i]));
else
dp[i][0][1].chk(dp[i - 1][0][1].upd(a[i - 1], a[i])), dp[i][1][1].chk(dp[i - 1][1][1].upd(a[i - 1], a[i]));
}
printf("%d\n", min({ dp[n][0][0].opt, dp[n][0][1].opt, dp[n][0][2].opt, dp[n][1][0].opt, dp[n][1][1].opt, dp[n][1][2].opt }));
}
signed main() {
File("lis");
int c, t;
scanf("%d%d", &c, &t);
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号