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;
}
posted @ 2025-02-27 09:53  UKE_Automation  阅读(42)  评论(0)    收藏  举报