250708 学习笔记

前言

毋自暴自弃,毋故步自封。

這一點現在就必須向黨內講明白,務必使同志們繼續保持謙虛、謹慎、不驕、不躁的作風。——*澤東

模拟赛

完蛋了。

# A B C D E
9 255 100 100 35 0 20
- 03:00:00 00:22:19 00:57:05 10:43:46 05:31:12 05:31:27

A. Pahuljice

注意到一个大小为 \(x\) 的雪花中有一个大小为 \(x-1\) 的雪花。所以可以通过从雪花中心尝试向外扩展的方法,来求出雪花的大小。

开一个 vector 记录所有 + 出现的位置,然后求出每个 + 所能扩展的最大范围就行。

#include <bits/stdc++.h>
using namespace std;
const int N = 55;
int n, m, a[N][N], ans = 0;
struct pos {
	int x, y;
};
vector<pos> h;
int dir[8][2] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}};
int ch[8] = {'\\', '|', '/', '-', '-', '/', '|', '\\'};
bool ok(int x, int y, int k) {
	return x + k >= 1 && x + k <= n && y + k >= 1 && y + k <= m && x - k >= 1 && x - k <= n && y - k >= 1 && y - k <= m;
}
int main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		string t;
		cin >> t;
		for (int j = 1; j <= m; j++) {
			a[i][j] = t[j - 1];
			if (a[i][j] == '+') h.push_back({i, j});
		}
	}
	int ans = 0;
	for (int i = 0; i < h.size(); i++) {
		int x = h[i].x, y = h[i].y;
		int j = 0, flag = false;
		for (j = 1; ok(x, y, j) && !flag; j++) {
			for (int k = 0; k < 8; k++) {
				int tx = x + j * dir[k][0], ty = y + j * dir[k][1];
				if (a[tx][ty] != ch[k]) {
					flag = true;
					break;
				}
			}
			if (flag) break;
		}
		ans = max(ans, j - 1);
	}
	cout << ans << '\n';
	return 0;
} 

B. Pingvin

最短路类的题目显然可以用 Dijkstra 算法解决。

#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n, sx, sy, sz, tx, ty, tz, a[N][N][N], dis[N][N][N], vis[N][N][N];
struct node {
	int x, y, z, len;
};
const bool operator < (const node &aa, const node &bb) {return aa.len > bb.len;}
priority_queue<node> q;
int dir[6][3] = {{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}};
bool ok(int x, int y, int z) {
	return x >= 1 && x <= n && y >= 1 && y <= n && z >= 1 && z <= n;
}
void dfs() {
	memset(dis, 0x3f, sizeof(dis));
	dis[sx][sy][sz] = 0;
	q.push({sx, sy, sz, 0});
	while (!q.empty()) {
		node f = q.top();
		q.pop();
		int x = f.x, y = f.y, z = f.z, w = f.len;
		if (vis[x][y][z]) continue;
		vis[x][y][z] = true;
		for (int i = 0; i < 6; i++) {
			int dx = x + dir[i][0], dy = y + dir[i][1], dz = z + dir[i][2];
			if (ok(dx, dy, dz) && !a[dx][dy][dz] && dis[dx][dy][dz] > w + 1) {
				dis[dx][dy][dz] = w + 1;
				q.push({dx, dy, dz, w + 1});
			}
		}
	}
}
int main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> sx >> sy >> sz >> tx >> ty >> tz;
	for (int z = 1; z <= n; z++) {
		for (int x = 1; x <= n; x++) {
			string s;
			cin >> s;
			for (int y = 1; y <= n; y++) a[x][y][z] = s[y - 1] - '0';
		}
	}
	dfs();
	cout << ((dis[tx][ty][tz] == 0x3f3f3f3f) ? -1 : dis[tx][ty][tz]) << '\n';
	return 0;
}

C. Dizalo

声明:因为这篇题解中有很多复杂概念,为了便于理解和美观性,使用了大量的直角引号,感谢理解。

观察一群人上、下电梯的过程,可以发现每次一群人出去后,最好的回电梯方式是按目标楼层从大到小。这样能让高层的人尽量少的让低层的人。

继续观察可以发现,导致他人多次下电梯的「罪魁祸首」,他们的目标楼层在原序列中是后缀最小值(在序列的某个后缀中的最小值),且他们会导致「在他们左侧」且「目标楼层大于他们的目标楼层」的人为他们多出电梯一次。例如,对于这组数据:

7 0
4 5 2 1 6 3 7

导致别人多次下电梯的人,他们的目标楼层分别是 \(3\)\(3\) 人)和 \(1\)\(3\) 人)。虽然在这里 \(7\) 也是后缀最小值,但是因为 \(7\) 的左侧没有比 \(7\) 小的数,所以别人不会为他而出电梯。

于是,我们已经得到了「最小的所有人下电梯总次数」的计算方法:\(n\)(每个人都要在自己的目标楼层下电梯)加上「每个后缀最小值左侧大于它的数的个数之和」。这个答案的计算方法有很多种,这里采用 \(3\) 个树状数组、\(1\) 棵线段树和 \(1\) 个红黑树(std::set)维护答案。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, q, a[N], tim[N], qq[N], del[N];
int bit[4][N], seg[N << 2], mk[N];
vector<int> v[N]; 
set<pair<int, int> > s;
long long sum = 0;
int lowbit(int x) {return x & -x;}
void add(int p, int x, int y) {
	while (x <= n) {
		bit[p][x] += y;
		x += lowbit(x);
	}
}
int query(int p, int x) {
	int res = 0;
	while (x) {
		res += bit[p][x];
		x -= lowbit(x);
	}
	return res;
}
void pushup(int rt) {seg[rt] = max(seg[rt << 1], seg[rt << 1 | 1]);}
void update(int rt, int l, int r, int s, int num) {
	if (l == r) {
		seg[rt] = num;
		return;
	}
	int mid = (l + r) >> 1;
	if (s <= mid) update(rt << 1, l, mid, s, num);
	else update(rt << 1 | 1, mid + 1, r, s, num);
	pushup(rt);
}
int query(int rt, int l, int r, int L, int R) {
	if (L <= l && r <= R) return seg[rt];
	if (l == r) return 0;
	int mid = (l + r) >> 1;
	int res = 0;
	if (mid >= L) res = max(res, query(rt << 1, l, mid, L, R));
	if (mid < R) res = max(res, query(rt << 1 | 1, mid + 1, r, L, R));
	return res;
}
int main() {
	// ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> q;
	for (int i = 1; i <= n; i++) tim[i] = q + 1;
	for (int i = 1; i <= n; i++) bit[1][i] = bit[2][i] = 1;
	for (int i = 1; i <= n; i++) if (i + lowbit(i) <= n) bit[1][i + lowbit(i)] = bit[2][i + lowbit(i)] += bit[1][i];
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= q; i++) {
		cin >> qq[i];
		tim[qq[i]] = i;
	}
	for (int i = n; i >= 1; i--) {
		// cout << query(1, 1, n, 1, a[i] - 1) << endl;
		v[query(1, 1, n, 1, a[i] - 1)].push_back(i);
		update(1, 1, n, a[i], tim[i]);
	} 
	s.insert({0, 0});
	s.insert({n + 1, n + 1});
	for (int i = 0; i < v[0].size(); i++) {
		int w = v[0][i];
		sum += w - a[w];
		add(3, w, 1);
		mk[w] = 1;
		s.insert({a[w], w});
	}
	cout << sum + n << ' ';
	for (int i = 1; i <= q; i++) {
		del[qq[i]] = 1;
		if (mk[qq[i]]) {
			add(3, qq[i], -1);
			sum -= query(1, qq[i]) - query(2, a[qq[i]]);
			s.erase({a[qq[i]], qq[i]});
		} else {
			sum -= query(3, (--s.lower_bound({a[qq[i]], 0})) -> second) - query(3, qq[i]);
		}
		add(1, qq[i], -1);
		add(2, a[qq[i]], -1);
		for (int j : v[i]) {
			if (del[j]) continue;
			add(3, j, 1);
			mk[j] = 1;
			s.insert({a[j], j});
			sum += query(1, j) - query(2, a[j]);
		}
		cout << sum + n - i << " ";
	}
	return 0; 
} 

D. Kuglice

看数据范围,很可能是 \(\mathcal{O}(n^2)\) 的动态规划。

按照常规思想设 \(dp[i][j]\) 为区间 \([l,r]\) 中的先手得分,可能不太好转移。所以设 \(dp[i][j]\) 为区间 \([l,r]\) 中的先手比后手多得的分。如果区间内有 \(t\) 种不同的数,则先手得分为 \(\frac{t+dp[i][j]}{2}\)。这样做是好转移的,因为它显然可以 \(dp[i+1][j]\)\(dp[i][j-1]\) 转移。注意到,能让选手加分的球一定是同种颜色球中最左侧或最右侧的。所以在转移时判断是否加分了即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 3005;
int n, cnt, a, b;
int k[N], lp[N], rp[N], dp[N][N];
int dfs(int l, int r) {
    if (l == r) return (lp[k[l]] >= l && rp[k[l]] <= l);
    if (dp[l][r] == 0x3f3f3f3f) dp[l][r] = max((lp[k[l]] >= l && rp[k[l]] <= r) - dfs(l + 1, r), (lp[k[r]] >= l && rp[k[r]] <= r) - dfs(l, r - 1));
    return dp[l][r];
}
int main() {
    cin >> n;
    if (n == 1) {
        cout << "1:0\n";
        return 0;
    }
    set<int> s;
    for (int i = 1; i <= n; i++) {
        cin >> k[i];
        s.insert(k[i]);
        if (!lp[k[i]]) {
            lp[k[i]] = i;
        }
        rp[k[i]] = i;
    }
    cnt = s.size();
    memset(dp, 0x3f3f3f3f, sizeof(dp));
    dfs(1, n);
    cout << (cnt + dp[1][n]) / 2 << ':' << cnt - (cnt + dp[1][n]) / 2 << '\n';
    return 0;
}

E. Zatopljenje

按照这类题目的传统,先把询问离线。考虑让海平面从高到低,逐渐下降的过程中有些岛屿变得相连,有些礁石露出海面。如果你做数据结构题比较多的话就会发现,线段树可以维护 \(0\)-\(1\) 数列中,区间连续的 \(1\) 的区间的个数。考虑设线段树刚开始所有位置都是 \(0\),从一个询问切换到另一个海平面更低的询问过程中,把露出海面的岛屿位置改为 \(1\),然后查询区间内的连续 \(1\) 区间个数。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, q, h[N], ans[N];
struct Query {
    int l, r, x, id;
} qu[N];
const bool operator < (const Query &a, const Query &b) {return a.x > b.x;}
struct Point {
    int h, id;
} p[N];
int tot = 1;
const bool operator < (const Point &a, const Point &b) {return a.h > b.h;}
int a[N], seg[N << 2];
void pushup(int rt, int l, int r) {
    int mid = (l + r) >> 1;
    seg[rt] = seg[rt << 1] + seg[rt << 1 | 1] - (a[mid] && a[mid + 1]);
}
void update(int rt, int l, int r, int s) {
    if (l == r) {
        seg[rt] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    if (s <= mid) update(rt << 1, l, mid, s);
    else update(rt << 1 | 1, mid + 1, r, s);
    pushup(rt, l, r);
}
int query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return seg[rt];
    int mid = (l + r) >> 1;
    int sum = 0;
    if (L <= mid) sum += query(rt << 1, l, mid, L, R);
    if (mid < R) sum += query(rt << 1 | 1, mid + 1, r, L, R);
    return sum - (L <= mid && mid < R && a[mid] && a[mid + 1]);
}
int main() {
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin >> n >> q;
    for (int i = 1; i <= n; i++) {
        cin >> h[i];
        p[i] = {h[i], i};
    }
    for (int i = 1; i <= q; i++) {
        cin >> qu[i].l >> qu[i].r >> qu[i].x;
        qu[i].id = i;
    }
    sort(qu + 1, qu + q + 1);
    sort(p + 1, p + n + 1);
    for (int i = 1; i <= q; i++) {
        while (tot <= n && p[tot].h > qu[i].x) {
            a[p[tot].id] = 1;
            update(1, 1, n, p[tot].id);
            tot++;
        }
        ans[qu[i].id] = query(1, 1, n, qu[i].l, qu[i].r);
    }
    for (int i = 1; i <= q; i++) cout << ans[i] << '\n';
    return 0;
}
posted @ 2025-07-09 19:32  cwkapn  阅读(13)  评论(0)    收藏  举报