2025 省选模拟 14
2025 省选模拟 14
得分
| T1 | T2 | T3 | 总分 | 排名 |
|---|---|---|---|---|
| \(100\) | \(50\) | \(20\) | \(170\) | \(1/7\) |
题解
T1 单峰序列
经典题,弱化版 \(t=0\) 的情况是某一场 CSP-S 模拟赛的 T1。考虑最小值一定是放到两边,所以我们从小到大去挪动数字,每一次选两边中距离更小的放。那么容易发现对于一个 \(a_i\),轮到它放的时候,放到左边需要的步数是它左边比它大的数的个数,同理放到右边的步数是右边比它大的数的个数。两者取最小值相加即可。
考虑怎样做前缀,不难发现随着前缀增长一位,每个数放到左边贡献不变,放到右边贡献可能加一。假如我们加入 \(a_i\),则 \(1\sim i\) 中 \(<a_i\) 的数放到右侧贡献会加一。那么我们可以维护出左侧 - 右侧贡献,当它减到 \(0\) 时就应该转向选左侧贡献,即上面的值不再变化。维护一个权值线段树实现上述操作即可,复杂度 \(O(n\log n)\)。
考场上写的比较若至,用了一个吉司机线段树维护全局减一、和 \(0\) 取 \(\max\) 操作,凭空多出一个 \(\log\)。
题外话:那场 CSP-S 模拟赛的 T4 是 CSP-S 2024 的 T3 原题。
#include <bits/stdc++.h>
#define int long long
namespace FastIO {
char buf[1 << 20], obuf[1 << 20], *p1 = buf, *p2 = buf, *p3 = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1++)
#define flush() (fwrite(obuf, 1, p3 - obuf, stdout))
#define putchar(x) (p3 == obuf + (1 << 20) && (flush(), p3 = obuf), *p3++ = (x))
class Flush{public:~Flush(){flush();}}_;
inline void read(int &x) {
x = 0; bool flg = 0; char ch = getchar();
while(!isdigit(ch)) flg ^= (ch == '-'), ch = getchar();
while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
flg ? x = -x : x;
}
inline void write(int x, int typ = 1) {
x < 0 ? x = -x, putchar('-') : 0;
static short stack[50], top = 0;
do stack[++top] = x % 10, x /= 10; while(x);
while(top) putchar(stack[top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
}
using namespace FastIO;
using namespace std;
const int Maxn = 5e5 + 5;
const int Inf = 2e9;
int n, ID;
int a[Maxn];
namespace BIT {
int c[Maxn];
void init() {for(int i = 1; i <= n; i++) c[i] = 0;}
int lowbit(int x) {return x & (-x);}
void mdf(int x, int val) {for(int i = x; i; i -= lowbit(i)) c[i] += val;}
int query(int x) {int sum = 0; for(int i = x; i <= n; i += lowbit(i)) sum += c[i]; return sum;}
}
int l[Maxn], r[Maxn];
void solve1() {
for(int i = 1; i <= n; i++) {
l[i] = BIT::query(a[i]); BIT::mdf(a[i], 1);
}
BIT::init();
int ans = 0;
for(int i = n; i >= 1; i--) {
r[i] = BIT::query(a[i]); BIT::mdf(a[i], 1);
ans += min(l[i], r[i]);
}
write(ans);
}
namespace SMT{
struct node {
int sum, mx, lmx, cmx;
int amx, add;
}t[Maxn << 2];
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
void Pushup(int p) {
t[p].sum = t[ls(p)].sum + t[rs(p)].sum;
if(t[ls(p)].mx > t[rs(p)].mx) {
t[p].mx = t[ls(p)].mx, t[p].cmx = t[ls(p)].cmx;
t[p].lmx = max(t[ls(p)].lmx, t[rs(p)].mx);
}
else if(t[ls(p)].mx < t[rs(p)].mx) {
t[p].mx = t[rs(p)].mx, t[p].cmx = t[rs(p)].cmx;
t[p].lmx = max(t[rs(p)].lmx, t[ls(p)].mx);
}
else {
t[p].mx = t[rs(p)].mx, t[p].cmx = t[ls(p)].cmx + t[rs(p)].cmx;
t[p].lmx = max(t[rs(p)].lmx, t[ls(p)].lmx);
}
}
void PushTag(int p, int l, int r, int amx, int add) {
t[p].amx += amx, t[p].add += add;
t[p].mx += amx;
if(t[p].lmx != -Inf) t[p].lmx += add;
t[p].sum += t[p].cmx * amx + (r - l + 1 - t[p].cmx) * add;
}
void PushDown(int p, int l, int r) {
int mid = (l + r) >> 1;
int mx = max(t[ls(p)].mx, t[rs(p)].mx);
PushTag(ls(p), l, mid, t[ls(p)].mx == mx ? t[p].amx : t[p].add, t[p].add);
PushTag(rs(p), mid + 1, r, t[rs(p)].mx == mx ? t[p].amx : t[p].add, t[p].add);
t[p].amx = t[p].add = 0;
}
void Build(int p, int l, int r) {
if(l == r) {
t[p] = {0, 0, -Inf, 1, 0, 0};
return ;
}
int mid = (l + r) >> 1;
Build(ls(p), l, mid), Build(rs(p), mid + 1, r);
Pushup(p);
}
void Add(int p, int l, int r, int pl, int pr, int v) {
if(pl > pr) return ;
if(pl <= l && r <= pr) {
PushTag(p, l, r, v, v);
return ;
}
PushDown(p, l, r);
int mid = (l + r) >> 1;
if(pl <= mid) Add(ls(p), l, mid, pl, pr, v);
if(pr > mid) Add(rs(p), mid + 1, r, pl, pr, v);
Pushup(p);
}
void Min(int p, int l, int r, int pl, int pr, int v) {
if(t[p].mx <= v || pl > pr) return ;
if(pl <= l && r <= pr && t[p].lmx < v) {
PushTag(p, l, r, v - t[p].mx, 0);
return ;
}
PushDown(p, l, r);
int mid = (l + r) >> 1;
if(pl <= mid) Min(ls(p), l, mid, pl, pr, v);
if(pr > mid) Min(rs(p), mid + 1, r, pl, pr, v);
Pushup(p);
}
}
void solve2() {
int sum = 0;
SMT::Build(1, 1, n);
for(int i = 1; i <= n; i++) {
int num = BIT::query(a[i]); sum += num;
BIT::mdf(a[i], 1);
SMT::Add(1, 1, n, a[i], a[i], -num);
SMT::Add(1, 1, n, 1, a[i] - 1, 1);
SMT::Min(1, 1, n, 1, a[i] - 1, 0);
int ans = sum + SMT::t[1].sum;
write(ans);
}
}
signed main() {
freopen("seq.in", "r", stdin);
freopen("seq.out", "w", stdout);
read(n), read(ID);
for(int i = 1; i <= n; i++) read(a[i]);
if(ID == 0) solve1();
else solve2();
return 0;
}
T2 划分线段
显然题目中的线段构成一个树形结构,而对于一个节点,其选出的线段没有什么限制,所以在该节点处考虑所选线段是比较困难的。考虑费用提前计算,我们在儿子的子树内就选好线段,到父亲处选即可。另一个问题是如果我们在儿子内就选好线段,父亲处所选线段是有可能从外部延伸进这个儿子的区间的。
为了解决两个问题,设 \(f(u,i,0/1,0/1)\) 表示 \(u\) 子树内选了 \(i\) 条子线段,钦定左 / 右侧是否有外部延伸进来的线段。如果有外部延伸进来的线段的话,我们在这个区间内先把一个端点的贡献算好,这样直接加起来就是答案。
直接转移是 \(O(n^3)\) 的,不过考虑到 \(u\) 子树内所选线段是有限制的。一方面,\(u\) 子树内所有区间都要选一个线段,因此 \(i\ge siz_u\);另一方面,\(u\) 子树内的 \(siz_u\) 个区间最多分出 \(2siz_u-1\) 个小段,而如果选择的区间数超过了这个一定不优,所以 \(i\le 2siz_u-1\)。于是 \(i\in[siz_u,2siz_u-1]\),我们枚举 \(i-siz_u\) 的值即可,复杂度就是 \(O(n^2)\) 的了。
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 5e3 + 5;
const int Inf = 1e9 + 10;
int n;
struct Seg {
int l, r;
}s[Maxn];
vector <int> E[Maxn];
int dp[Maxn][Maxn][2][2];
int f[2][Maxn][2][2], siz[Maxn];
void dfs(int x) {
siz[x] = 1;
if(!E[x].size()) {
dp[x][0][0][0] = s[x].r - s[x].l;
dp[x][0][1][0] = s[x].r;
dp[x][0][0][1] = -s[x].l;
dp[x][0][1][1] = 0;
return ;
}
int now = 0;
sort(E[x].begin(), E[x].end(), [](int x, int y){return s[x].l < s[y].l;});
for(auto to : E[x]) dfs(to);
for(auto to : E[x]) {
now ^= 1;
if(to == E[x][0]) {
for(int i = 0; i <= n; i++)
for(int j = 0; j < 2; j++)
for(int k = 0; k < 2; k++) f[now][i][j][k] = dp[to][i][j][k];
siz[x] += siz[to];
continue;
}
for(int i = 0; i <= siz[x] + siz[to] + 1; i++)
for(int p = 0; p < 2; p++)
for(int q = 0; q < 2; q++)
f[now][i][p][q] = -Inf;
for(int i = 0; i < siz[x]; i++)
for(int j = 0; j < siz[to]; j++)
for(int p = 0; p < 2; p++)
for(int q = 0; q < 2; q++)
for(int r = 0; r < 2; r++)
f[now][i + j + q][p][r] = max(f[now][i + j + q][p][r], f[now ^ 1][i][p][q] + dp[to][j][q][r]);
siz[x] += siz[to];
}
for(int i = 0; i < siz[x]; i++) {
for(int p = 0; p < 2; p++) {
for(int q = 0; q < 2; q++) {
dp[x][i][p][q] = f[now][i + 1][p][q];
if(p) dp[x][i][0][q] = max(f[now][i][p][q] - s[x].l, dp[x][i][0][q]);
if(q) dp[x][i][p][0] = max(f[now][i][p][q] + s[x].r, dp[x][i][p][0]);
if(p || q) dp[x][i][p][q] = max(dp[x][i][p][q], f[now][i][p][q]);
if(p && q && i) {
dp[x][i][0][0] = max(dp[x][i][0][0], f[now][i - 1][p][q] + s[x].r - s[x].l);
dp[x][i][p][q] = max(dp[x][i][p][q], f[now][i - 1][p][q]);
}
}
}
}
}
int main() {
freopen("segment.in", "r", stdin);
freopen("segment.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> s[i].l >> s[i].r;
}
sort(s + 1, s + n + 1, [](Seg x, Seg y){return x.l < y.l;});
s[0] = {-Inf, Inf};
for(int i = 1; i <= n; i++) {
for(int j = i - 1; j >= 0; j--) {
if(s[j].l < s[i].l && s[i].r < s[j].r) {
E[j].push_back(i); break;
}
}
}
for(int i = 1; i <= n; i++) {
for(int j = 0; j <= n; j++) {
for(int p = 0; p < 2; p++) {
for(int q = 0; q < 2; q++) dp[i][j][p][q] = -Inf;
}
}
}
int ans = 0;
for(auto x : E[0]) {
dfs(x);
ans += dp[x][0][0][0];
}
cout << ans << '\n';
return 0;
}
T3 红蓝树
考虑题目中给出的操作本质,不难发现一个点最多影响其弹栈后加入的第一个点。那么我们将每个点往其弹栈后加入的第一个点连边(如果未弹栈或没有这样的点向虚根连边),如此会形成一个树形结构。这样每一个蓝点会对它根链上所有点作出贡献。
那么对于询问区间 \([l,r]\) 实际上就是询问出现时间在 \([l,r]\) 内的点构成的子图中有多少个点的子树内有蓝点。首先我们需要找到这些点,不难发现的是,我们一定存在一种方案使得遍历这棵树的 DFS 序倒序为原来的出现时间。那么这个区间在树上就是一个连续的 \([l',r']\) DFN 区间,求出这个的答案即可。
我们要知道有多少个点的子树中有蓝点,显然可以求出区间中所有蓝点到虚根构成的虚树大小,由于 DFN 连续,这个可以直接用线段树进行维护。但是这样的话我们会多算一些点,手玩后不难发现多算的点形成了一条根链,起点是 DFN 为 \(l'-1\) 的点 \(u\) 和 \([l',r']\) 中 DFN 最小的蓝点的 LCA。把这一段的贡献减掉即可。
最后还剩下修改,这个其实不难,只需要实现区间反转的操作,那么我们在线段树上多维护一下红点的虚树大小即可。使用 \(O(1)\) LCA 的复杂度 \(O(n\log n)\)。
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 6e5 + 5;
const int Inf = 2e9;
int n, q, a[Maxn];
int head[Maxn], edgenum;
struct node {
int nxt, to;
}edge[Maxn];
void add(int u, int v) {
edge[++edgenum] = {head[u], v};
head[u] = edgenum;
}
int id[Maxn];
int dfn[Maxn], idx, dep[Maxn], rnk[Maxn], st[21][Maxn];
void dfs(int x, int fth) {
rnk[idx] = x;
st[0][dfn[x] = idx++] = fth;
dep[x] = dep[fth] + 1;
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
dfs(to, x);
}
}
int s1[Maxn], top1;
int s2[Maxn], top2;
int m = 0;
int _min(int x, int y) {return dfn[x] < dfn[y] ? x : y;}
void init() {
for(int i = 1; i <= n; i++) {
if(a[i] != 3) {
s1[++top1] = ++m; id[m] = i;
while(top2) add(m, s2[top2--]);
}
else {
if(top1) s2[++top2] = s1[top1--];
}
}
for(int i = 1; i <= top1; i++) add(0, s1[i]);
while(top2) add(0, s2[top2--]);
dep[0] = -1; dfs(0, 0);
for(int i = 1; i <= 20; i++) {
for(int j = 1; j + (1 << i) - 1 <= n; j++) {
st[i][j] = _min(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
}
}
}
int Lca(int u, int v) {
if(u == v) return u;
if((u = dfn[u]) > (v = dfn[v])) swap(u, v);
u++; int k = __lg(v - u + 1);
return _min(st[k][u], st[k][v - (1 << k) + 1]);
}
struct VT {
int mn, mx, lca, siz;
VT operator + (const VT &b) const {
if(!siz) return b;
if(!b.siz) return *this;
return {mn, b.mx, Lca(lca, b.lca), siz + b.siz - dep[Lca(rnk[mx], rnk[b.mn])]};
}
};
namespace SMT {
struct node {
VT R, B;
int tag;
}t[Maxn << 2];
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
void pushup(int p) {
t[p].R = t[ls(p)].R + t[rs(p)].R;
t[p].B = t[ls(p)].B + t[rs(p)].B;
}
void pushtag(int p) {swap(t[p].R, t[p].B); t[p].tag ^= 1;}
void pushdown(int p) {if(t[p].tag) pushtag(ls(p)), pushtag(rs(p)), t[p].tag = 0;}
void build(int p, int l, int r) {
if(l == r) {
if(a[id[rnk[l]]] == 1) t[p].R = {l, l, rnk[l], dep[rnk[l]]};
else t[p].B = {l, l, rnk[l], dep[rnk[l]]};
return ;
}
int mid = (l + r) >> 1;
build(ls(p), l, mid), build(rs(p), mid + 1, r);
pushup(p);
}
void mdf(int p, int l, int r, int pl, int pr) {
if(pl <= l && r <= pr) {
pushtag(p); return ;
}
pushdown(p);
int mid = (l + r) >> 1;
if(pl <= mid) mdf(ls(p), l, mid, pl, pr);
if(pr > mid) mdf(rs(p), mid + 1, r, pl, pr);
pushup(p);
}
void query(int p, int l, int r, int pl, int pr, VT &res) {
if(pl <= l && r <= pr) {
res = res + t[p].B;
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
if(pl <= mid) query(ls(p), l, mid, pl, pr, res);
if(pr > mid) query(rs(p), mid + 1, r, pl, pr, res);
}
}
int main() {
freopen("rbtree.in", "r", stdin);
freopen("rbtree.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> q;
for(int i = 1; i <= n; i++) cin >> a[i];
init();
SMT::build(1, 1, m);
while(q--) {
int opt, l, r;
cin >> opt >> l >> r;
l = lower_bound(id + 1, id + m + 1, l) - id;
r = upper_bound(id + 1, id + m + 1, r) - id - 1;
switch(opt) {
case 1: {
if(l > r) continue;
SMT::mdf(1, 1, m, dfn[r], dfn[l]);
break;
}
case 2: {
if(l > r) {cout << "0\n"; continue ;}
VT ans = {0, 0, 0, 0};
SMT::query(1, 1, m, dfn[r], dfn[l], ans);
int p = dep[Lca(rnk[dfn[r] - 1], rnk[ans.mn])];
int res = ans.siz - p;
cout << res << '\n';
break;
}
}
}
return 0;
}

浙公网安备 33010602011771号