【模拟赛总结】2025年12月21日 模拟赛总结
蔓延
很简单,使用 BFS 填充,然后让神圣草方块优先即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e3 + 10;
struct node {
int type, tim, x, y;
};
int n, m, vis[N][N];
char a[N][N];
int dx[4] = { 0, 1, 0, -1 };
int dy[4] = { 1, 0, -1, 0 };
void bfs() {
queue<node> q;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] == 'H') q.push({ 1, 0, i, j }), vis[i][j] = 1; // 神圣草方块
if (a[i][j] == 'C') q.push({ 0, 0, i, j }), vis[i][j] = 0; // 腐化草方块
}
}
while (!q.empty()) {
vector<node> vec;
int tt = q.front().tim;
while (!q.empty() && q.front().tim == tt) {
int type = q.front().type;
int x = q.front().x;
int y = q.front().y;
vec.push_back({ type, tt, x, y });
q.pop();
}
for (auto v : vec) {
if (v.type == 0) continue;
int x = v.x, y = v.y;
for (int i = 0; i < 4; i++) {
int tx = x + dx[i];
int ty = y + dy[i];
if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
if (vis[tx][ty] >= 0) continue;
vis[tx][ty] = 1;
q.push({ 1, tt + 1, tx, ty });
}
}
for (auto v : vec) {
if (v.type == 1) continue;
int x = v.x, y = v.y;
for (int i = 0; i < 4; i++) {
int tx = x + dx[i];
int ty = y + dy[i];
if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
if (vis[tx][ty] >= 0) continue;
vis[tx][ty] = 0;
q.push({ 0, tt + 1, tx, ty });
}
}
}
}
int main() {
// ios::sync_with_stdio(0);
// cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
for (int j = 1; j <= m; j++)
a[i][j] = s[j - 1];
}
// for (int i = 1; i <= n; i++) {
// for (int j = 1; j <= m; j++)
// cout << a[i][j];
// cout << endl;
// }
memset(vis, -1, sizeof(vis));
bfs();
for (int i = 1; i <= n; i++, puts(""))
for (int j = 1; j <= m; j++)
cout << (vis[i][j] ? 'H' : 'C');
return 0;
}
一起种草药
考虑到如果一个植物需要成长的时间越短,被采摘的概率就越大,因此我们贪心考虑成长时间最小的植物。
假设我们现在遇到一个植物,它需要的成长时间为 \(t_i\),则我们可以将它放在第 \(t_i\) 个位置。如果那个位置已经被占了,就往后移,直到找到空位,再把它种下去。
这里寻找空位用树状数组 \(+\) 二分就可以了,时间复杂度为 \(O(n(\log n)^2)\)。
#include <bits/stdc++.h>
using namespace std;
int lowbit(int x) {
return x & -x;
}
const int N = 1e5 + 10;
int n, a[N];
int tree[N];
void insert(int x, int d) {
while (x <= n) {
tree[x] += d;
x += lowbit(x);
}
}
int get(int x) {
int res = 0;
while (x) {
res += tree[x];
x -= lowbit(x);
}
return res;
}
int find(int x) {
if (get(n) - get(x - 1) == n - (x - 1)) return -1;
int l = x - 1, r = n, ans = x - 1;
while (l <= r) {
int mid = (l + r) >> 1;
if (get(mid) - get(x - 1) < mid - (x - 1)) r = mid - 1;
else ans = mid, l = mid + 1;
// cout << l << " " << r << endl;
}
return ans + 1;
}
void solve() {
for (int i = 1; i < N; i++)
tree[i] = 0;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1);
int ans = 0;
for (int i = 1; i <= n; i++) {
if (a[i] > n) break;
int x = find(a[i]);
// cout << get(n) << " " << get(a[i] - 1) << " " << x << endl;
// cout << "---------------------------------------------\n";
if (x != -1) insert(x, 1), ans++;
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) solve();
return 0;
}
幸福的NPC
这道题简单来说就是在树上找一个最大的连通块,使得这个连通块里的所有结点的权值都不小于连通块的大小。
看到最值,先二分答案,将一个不确定的大小转变为一个确定的大小。
然后就是 check() 了,贪心明显不行,转而考虑 DP。
假设我们认为最大连通块的大小是 \(x\)。
- 状态:定义 \(f_i\) 表示在以 \(i\) 为根的子树中,最大的包含 \(i\) 并且满足条件的连通块大小(如果 \(i\) 不满足,则 \(f_i=0\)。此处的满足条件指的是结点权值不小于 \(x\))
- 状态转移方程:\(f_i=f_i+\sum f_{son}\),此处 \(son\) 表示 \(i\) 的儿子
- 初始化:\(f_i=1(a_i\ge x)\)
根据定义,如果存在 \(f_i\) 满足 \(f_i\ge x\),则说明 \(x\) 是合法的。反之,则是不合法的。
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int N = 3e5 + 10;
int n, a[N], f[N];
vector<int> linker[N];
void dfs(int x, int fa, int p) {
f[x] = 1;
for (int v : linker[x]) {
if (v == fa) continue;
dfs(v, x, p);
f[x] += f[v];
}
if (a[x] < p) f[x] = 0;
}
bool check(int x) {
memset(f, 0, sizeof(f));
dfs(1, 0, x);
for (int i = 1; i <= n; i++)
if (f[i] >= x) return 1;
return 0;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
linker[u].push_back(v);
linker[v].push_back(u);
}
int l = 1, r = n, ans = 1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) ans = mid, l = mid + 1;
else r = mid - 1;
}
// cout << check(1) << endl;
cout << ans << endl;
return 0;
}
300颗够吗
这题考虑贪心。
首先肯定先打能赚钱的 Boss,再打亏钱的 Boss。
- 对于能赚钱的 Boss,肯定先打 \(a_i\) 小的,毕竟如果你小的都打不下来,大的肯定就不用说了
- 对于不能赚钱的 Boss,要先打 \(b_i\) 大的,这里给出证明:
假设我们当前金币数为 \(x\),这里有两个 Boss,分别是 \(i\) 和 \(j\)。由于交换相邻两个 Boss 的位置,不会影响别的 Boss,因此考虑交换。
如果我们先打 \(i\),再打 \(j\),那么我们就要做到 \(x-a_i+b_i\ge a_j\),即 \(b_i\ge a_i+a_j-x\)。
如果我们先打 \(j\),再打 \(i\),那么我们就要做到 \(x-a_j+b_j\ge a_i\),即 \(b_j\ge a_i+a_j-x\)。
很明显,这里 \(b\) 值越大,则成功的可能性就越大,因此按 \(b\) 的大小从小到大排序哪些不能赚钱的 Boss。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
struct node {
int a, b, type;
} p[N];
int n, m;
bool cmp1(node x, node y) {
if (x.type == y.type) return x.a < y.a;
return x.type > y.type;
}
bool cmp2(node x, node y) {
if (x.type == y.type) return x.b > y.b;
return x.type < y.type;
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> p[i].a >> p[i].b;
if (p[i].a > p[i].b) p[i].type = 0;
if (p[i].a < p[i].b) p[i].type = 1;
}
sort(p + 1, p + n + 1, cmp1);
int res = m;
for (int i = 1; i <= n; i++) {
if (!p[i].type) break;
if (res >= p[i].a) res = res - p[i].a + p[i].b;
else {
cout << "No\n";
return ;
}
}
sort(p + 1, p + n + 1, cmp2);
for (int i = 1; i <= n; i++) {
if (p[i].type) break;
if (res >= p[i].a) res = res - p[i].a + p[i].b;
else {
cout << "No\n";
return ;
}
}
cout << "Yes\n";
}
signed main() {
// ios::sync_with_stdio(0);
// cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) solve();
return 0;
}
拜月
这题仍然使用 DP。
- 首先先写原始 DP
- \(f[i][j]\) 表示以第 \(i\) 天为开始的 \(j\) 天中,有多少中合法的方案
- 延长序列(\(j\) 变 \(j + 1\)):\(f[i + 1][j + 1] += f[i][j] \times (m - j)\)
- 截断序列(\(j\) 变 \(k\)):\(f[i + 1][k] += f[i][j](1 \le k \le j)\)
- 答案:sum(f[n][i])
- 矩阵优化 DP(状态从 \(p\) 变为 \(q\))
- 延长序列:如果 \(q = p + 1\),即 \(p = q - 1\),因此系数为 \(m - (p + 1) = m - r\)
- 截断序列:如果 \(r <= c\),即 \(c >= r\),则系数为 \(1\)
注意这里的 \(T\) 是转移矩阵,并非我们的初始矩阵,初始矩阵为 \({m, 0, 0, 0...0} ^ n\)。
/*
1. 首先先写原始 DP
- f[i][j] 表示以第 i 天为开始的 j 天中,有多少中合法的方案
- 延长序列(j 变 j + 1):f[i + 1][j + 1] += f[i][j] * (m - j)
- 截断序列(j 变 k):f[i + 1][k] += f[i][j](1 <= k <= j)
- 答案:sum(f[n][i])
2. 矩阵优化 DP(状态从 p 变为 q)
- 延长序列:如果 q = p + 1,即 p = q - 1,因此系数为 m - (p + 1) = m - r
- 截断序列:如果 r <= c,即 c >= r,则系数为 1
注意这里的 T 是转移矩阵,并非我们的初始矩阵,初始矩阵为 {m, 0, 0, 0...0} ^ T
*/
#include <bits/stdc++.h>
#define ll long long
#define Matrix vector<vector<ll> >
using namespace std;
const int mod = 1e9 + 7;
ll n, m;
// 矩阵乘法
Matrix multiply(const Matrix& A, const Matrix& B) {
int n = A.size();
Matrix C(n, vector<ll>(n, 0));
for (int i = 0; i < n; i++) {
for (int k = 0; k < n; k++) {
if (A[i][k] == 0) continue;
for (int j = 0; j < n; j++)
C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % mod;
}
}
return C;
}
// 矩阵快速幂
Matrix power(Matrix A, ll p) {
int n = A.size();
Matrix Res(n, vector<ll>(n, 0));
for (int i = 0; i < n; i++) Res[i][i] = 1; // 单位矩阵
while (p > 0) {
if (p & 1) Res = multiply(Res, A);
A = multiply(A, A);
p >>= 1;
}
return Res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
if (n == 1ll) {
cout << m % mod << endl;
return 0;
}
int size = m - 1;
Matrix T(size, vector<ll>(size, 0));
for (int r = 0; r < size; ++r) {
for (int c = 0; c < size; ++c) {
if (c == r - 1) T[r][c] = (T[r][c] + (m - (c+1))) % mod;
if (c >= r) T[r][c] = (T[r][c] + 1) % mod;
}
}
Matrix Tn = power(T, n - 1);
ll ans = 0;
for (int i = 0; i < size; i++)
ans = (ans + Tn[i][0]) % mod;
// 最后乘上初始的 m 种情况
ans = (ans * m) % mod;
cout << ans << endl;
return 0;
}

浙公网安备 33010602011771号