10.28/2023 做题记录
Stoned Game
简单的博弈论,没什么好说的。
CF1385F
换根 DP,但是有更加简洁的做法:直接用队列维护每个节点叶子节点数。
CF1396C
没想到,是我 native 了, 这题需要仔细观察性质。
- 首先 $AWP$ 肯定只用来打 $BOSS$。
- 每次只在相邻层来回移动。
- 可以构造方案使得在 $n$ 或,$n - 1$ 层关时间最少。 然后就直接 $DP$。值得一做。
CF1379F1
我们以每个 $2 \times 2$ 的格子来考虑放置国王,发现可以放在左上 $L$ 或者右下 $R$ 中,不合法仅当 $L$ 与 $R$ 对角相邻,然后直接二分答案。
CF1401F
做完可以对线段树有个更深入的认识。
CF1404C
重点谈谈这道题。
考虑没有限制怎么做:先设 $d_i = i - a_i$,然后我们发现当 $d_i = 0$ 时,这个点可以删除。而后面的点的 $d_i$ 都会减 $1$。考虑贪心,我们每次删除最靠后的 $d_i = 0$ 的点,正确性显然。
我们发现对于后面的限制比较好解决,只需要不考虑它们对答案的贡献。
- 发现当前面限制的点越多,后面的点越难被删除。
- 每个点需要前面删除 $i - a_i$ 个点才能被删除。
设 $f_i$ 表示以点 $f_i$ 开始能删除的点数。
我们从前往后考虑,设当前加入的点为 $r$,
由 $1$,容易发现 $f_i$ 具有单调性,二分求出 $mid$,满足 $ f_{mid} < d_r < f_{mid + 1} $。 然后给 $f_{1 \sim mid}$ 加上 $1$。
实际上 $f_i$ 也可以表示要删除点 $i$ 至多锁定的点数 $\lim_i$。(不过我不是很清楚)
我们把查询离线,从前往后维护 $f_i$ \ $\lim_i$。复杂度 $O(n \log n)$。
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 500;
int c[N], n, m;
int lg[N], a[N];
vector <int> qr[N], qi[N];
int ans[N];
void add(int x, int v) {
while(x <= n) c[x] += v, x += x & -x;
}
int query(int x) {
int ans = 0;
while(x) ans += c[x], x -= x & -x;
return ans;
}
int Sear(int r, int v) {
int sum = 0, p = 0;
for(int t = 1 << lg[r]; t >= 1; t >>= 1) {
if(p + t <= r && sum + c[p + t] >= v) {
p += t;
sum += c[p];
}
}
return p;
}
int main() {
scanf("%d %d", &n, &m);
lg[0] = -1; for(int i = 1; i <= n; ++i) lg[i] = lg[i >> 1] + 1;
for(int i = 1;i <= n; ++i) {
scanf("%d", &a[i]), a[i] = i - a[i];
}
for(int i = 1;i <= m; ++i) {
int l, r;
scanf("%d %d", &l, &r);
qr[n - r].push_back(l + 1);
qi[n - r].push_back(i);
}
for(int i = 1; i <= n; ++i) {
if(a[i] > 0) {
int r = Sear(i - 1, a[i]);
if(r >= 1) add(1, 1), add(r + 1, -1);
}
else if(a[i] == 0) add(1, 1), add(i + 1, -1);
int len = qr[i].size();
for(int j = 0; j < len; ++j) {
ans[qi[i][j]] = query(qr[i][j]);
}
}
for(int i = 1;i <= m; ++i) printf("%d\n", ans[i]);
return 0;
}

浙公网安备 33010602011771号