【模拟赛总结】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。

  1. 首先先写原始 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])
  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} ^ 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;
}
posted @ 2025-12-21 11:29  chrispang  阅读(2)  评论(0)    收藏  举报