算法题刷题日记

Github 链接:Daily Practice

文章目录

  • 2025.6.16
    • 1. 洛谷 P1043 [NOIP 2003 普及组] 数字游戏
    • 2. 洛谷 P1121 环状最大两段子段和
    • 3. Codeforces Round 1031 A. Shashliks
    • 4. Codeforces Round 1031 B. Good Start
    • 5. Codeforces Round 1031 C. Smilo and Minecraft
  • 2025.6.17
    • 1. 洛谷 P1126 机器人搬重物
  • 2025.6.20
    • 1. 洛谷 P1198 [JSOI2008] 最大数
    • 2. P1314 [NOIP 2011 提高组] 聪明的质监员
  • 2025.6.21
    • 1. 洛谷 P1343 地震逃生
    • 2. 洛谷 P1792 [国家集训队] 种树
    • 3. 洛谷 P1903 [国家集训队] 数颜色 / 维护队列
    • 4. 洛谷 P1575 正误问题
  • 2025.6.24
    • 1. 洛谷 P2389 电脑班的裁员
  • 2025.6.30
    • 1. 洛谷 P1360 [USACO07MAR] Gold Balanced Lineup G
    • 2. 洛谷 P1472 [USACO2.3] 奶牛家谱 Cow Pedigrees
    • 3. P1545 [USACO04DEC] Dividing the Path G

2025.6.16

1. 洛谷 P1043 [NOIP 2003 普及组] 数字游戏

题目链接:洛谷 P1043

题意:将 n n n ( n ≤ 50 n \le 50 n50) 个环形数字分成 m m m ( m ≤ 9 m \le 9 m9) 组,求每一组和在模 10 10 10 后乘积的最小值和最大值。

错误解法和代码

利用 dfs 找出每一个分组的边界,再对数字维护一个前缀和,暴力求出每一种分组方案的结果。由于这种做法的时间复杂度是 O ( n m ) O(n^m) O(nm) 最后导致了 TLE

#include 
using namespace std;

int n, m, a[55], pre[55], maxm = 0, minm = INT_MAX;

void dfs(int cur, vector<int>& v) {
    if (v.size() >= m) {
        int res = (pre[v[0]] + pre[n] - pre[v.back()] + 1000000) % 10;
        for (int i = 1; i < v.size(); i++)
            res *= ((pre[v[i]] - pre[v[i - 1]] + 1000000) % 10);
        maxm = max(maxm, res), minm = min(minm, res);
        return;
    }
    if (cur >= n) return;
    for (int i = cur + 1; i <= n; i++) {
        v.push_back(i);
        dfs(i, v);
        v.pop_back();
    }
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + a[i];
    vector<int> v;
    dfs(0, v);
    cout << minm << endl << maxm << endl;
    return 0;
}

正确解法和代码

区间 D P DP DP:将环形数字倍长之后可以将其当作线形数字来处理。设 maxm l , r , k \text{maxm}_{l, r, k} maxml,r,k, minm l , r , k \text{minm}_{l, r, k} minml,r,k 分别为 [ l , r ] [l, r] [l,r] 区间内,将线性数字分为 k k k 段的最大、最小值,具体状态转移方程看代码。

时间复杂度: O ( n 3 m ) O(n^3m) O(n3m)

#include 
using namespace std;

#define int long long

int n, m, a[105], maxm[105][105][10], minm[105][105][10];

signed main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i], a[i + n] = a[i];
    for (int i = 1; i <= 2 * n; i++) a[i] += a[i - 1];
    for (int i = 1; i <= 100; i++)
		for (int j = 1; j <= 100; j++)
			for (int k = 0; k < 10; k++) maxm[i][j][k] = 0, minm[i][j][k] = 1e9;
    // cout << minm[0][0][0];
    for (int i = 1; i <= 2 * n; i++)
        for (int j = 1; j <= 2 * n; j++) {
            maxm[i][j][1] = minm[i][j][1] = ((a[j] - a[i - 1]) % 10 + 10) % 10;
		}
    for (int len = 2; len <= n; len++) {
        for (int l = 1; l <= n; l++) {
			int r = l + len - 1;
            for (int k = 2; k <= m; k++) {
				if (len < k) continue;
                for (int p = l; p < r; p++) {
                    maxm[l][r][k] = max(maxm[l][r][k], maxm[l][p][k - 1] * maxm[p + 1][r][1]);
                    minm[l][r][k] = min(minm[l][r][k], minm[l][p][k - 1] * minm[p + 1][r][1]);
                }
            }
        }
    }
    int ans1 = LLONG_MAX, ans2 = 0;
    for (int i = 1; i <= n; i++)
        ans1 = min(ans1, minm[i][i + n - 1][m]), ans2 = max(ans2, maxm[i][i + n - 1][m]);
	// for (int i = 1; i <= n; i++) cout << minm[i][i + n - 1][m] << " ";
    cout << ans1 << endl << ans2 << endl;
    return 0;
}

2. 洛谷 P1121 环状最大两段子段和

题目链接:洛谷 P1121

题意:给出一段长度为 n n n 的环形序列,选出两个不重合的非空段,使得两段的和最大。

错误解法及代码

将环形序列倍长当成线性序列处理。设 d p i , 0 / 1 / 2 dp_{i, 0/1/2} dpi,0/1/2 表示以 i i i 结尾选取了 0 / 1 / 2 0/1/2 0/1/2 个子段的最大和。状态转移方程为 d p i , 1 = max ⁡ ( d p i − 1 , 1 + a i , a i ) dp_{i, 1} = \max(dp_{i - 1, 1} + a_i, a_i) dpi,1=max(dpi1,1+ai,ai) d p i , 2 = max ⁡ ( d p i − 1 , 2 + a i , max ⁡ j = l i − 1 d p j , 1 + a i ) dp_{i, 2} = \max(dp_{i - 1, 2} + a_i, \max_{j = l}^{i - 1}dp_{j, 1} + a_i) dpi,2=max(dpi1,2+ai,maxj=li1dpj,1+ai)。时间复杂度为 O ( n 2 ) O(n^2) O(n2) 导致了 TLE,并且可能做法或者代码导致有误导致了 WA

#include 
using namespace std;

const int N = 4e5 + 5;
int n, a[N], dp[N][3], maxm[N], ans = 0;

int main() {
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i], a[i + n] = a[i];
	for (int l = 1; l <= n; l++) {
		maxm[l] = dp[l][1] = a[l], dp[l][0] = dp[l][2] = 0;
		for (int i = l + 1; i <= l + n - 1; i++) {
			dp[i][1] = max(dp[i - 1][1] + a[i], a[i]);
			dp[i][2] = max({dp[i - 1][2] + a[i], maxm[i - 1] + a[i], dp[i - 1][1] + a[i]});
			maxm[i] = max(maxm[i - 1], dp[i][1]);
			ans = max(ans, dp[i][2]);
		}
	}
	cout << ans << endl;
	return 0;
}

正确解法及代码

对于一个长度为 n n n 的环形序列,选取两个最大子段使其和最大,只有可能有以下两种选法(用 1 1 1 表示选择对应元素, 0 0 0 表示不选对应元素):

  1. 00011110000111100
  2. 111000011110000111

对于第 1 1 1 种情况,直接利用 d p dp dp 分别处理出前缀、后缀的最大子段和,然后枚举分界点找出两段和的最大值即可。对于第二种情况,我们可以不找 1 1 1 的部分,去找 0 0 0 的部分,也就是将原序列全部变为相反数后,再找两段最大的子段和,然后再将该结果加上原序列的总和即为答案。

特判:整个序列只有一个正数时,答案就是序列中两个最大的值的和。

时间复杂度: O ( n ) O(n) O(n)

#include 
using namespace std;

#define int long long

const int N = 2e5 + 5;
int n, a[N], pre[N], suf[N], sum = 0, num = 0;

int query() {
	for (int i = 1; i <= n; i++) pre[i] = max(pre[i - 1], 0LL) + a[i];
	for (int i = 2; i <= n; i++) pre[i] = max(pre[i], pre[i - 1]);
	for (int i = n; i; i--) suf[i] = max(suf[i + 1], 0LL) + a[i];
	for (int i = n - 1; i; i--) suf[i] = max(suf[i], suf[i + 1]);
	int res = LLONG_MIN;
	for (int i = 1; i < n; i++) res = max(res, pre[i] + suf[i + 1]);
	return res;
}

signed main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i], sum += a[i], num += (a[i] > 0);
	int res1 = query();
	if (num == 1) cout << res1 << endl;
	else {
		for (int i = 1; i <= n; i++) a[i] = -a[i];
		int res2 = sum + query();
		if (!res2) res2 = LLONG_MIN;
		cout << max(res1, res2) << endl;
	}
	return 0;
}

3. Codeforces Round 1031 A. Shashliks

题目链接:Codeforces Round 1031 A

题意:给定一个初始温度 k k k,当温度大于等于 a a a 时,可以进行第一种操作使温度下降 x x x 度;当温度大于等于 b b b 时,可以进行第二种操作使温度下降 y y y 度。求最多操作次数。

正确思路及代码

贪心。

不妨假设 a > b a > b a>b ( a ≤ b a \le b ab 时分别交换 ( a , b ) (a, b) (a,b) ( x , y ) (x, y) (x,y) 即可)

  • x > y x > y x>y 时,一直进行第二种操作是最优的操作。
  • x < y x < y x<y 时,先进行第一种操作直到温度 k < a k < a k<a 再进行第二种操作是最优的操作。

时间复杂度: O ( 1 ) O(1) O(1)

#include 
using namespace std;

#define int long long

void solve() {
    int k, a, b, x, y;
    cin >> k >> a >> b >> x >> y;
    if (a < b) swap(a, b), swap(x, y);
    int res = 0;
    if (x < y) {
        int res1 = max(0LL, (k - a + x) / x), res2 = max(0LL, (k - res1 * x - b + y) / y);
        res = max(res1 + res2, res);
    } else {
        res = max(res, (k - b + y) / y);
    }
    cout << res << "\n";
}

signed main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}

4. Codeforces Round 1031 B. Good Start

题目链接:Codeforces Round 1031 B

题意:平面内给定一个从 ( 0 , 0 ) (0, 0) (0,0) ( w , h ) (w, h) (w,h) 的矩形,需要用大小为 a × b a \times b a×b 纸张去覆盖整个矩形,纸张可以超出矩形边界,但是不能旋转、不能重叠。现在矩形内已经放有两张纸,问是否能在不移动这两张纸的前提下,按照要求用纸张覆盖整个矩形。

正确思路及代码

( x 1 − x 2 ) % a = 0 (x_1 - x_2) \% a = 0 (x1x2)%a=0 ( y 1 − y 2 ) % b = 0 (y_1 - y_2) \% b = 0 (y1y2)%b=0 时输出 Yes

特判:当 x 1 = x 2 x_1 = x_2 x1=x2 y 1 = y 2 y_1 = y_2 y1=y2 时需要判断另一个不相等的坐标的差是否能整除 a a a 或者 b b b

时间复杂度: O ( 1 ) O(1) O(1)

#include 
using namespace std;

#define int long long

int w, h, a, b, x1, Y1, x2, y2;

void solve() {
    cin >> w >> h >> a >> b >> x1 >> Y1 >> x2 >> y2;
    if (x1 == x2) {
        if (abs(Y1 - y2) % b == 0) cout << "Yes\n";
        else cout << "No\n";
    } else if (Y1 == y2) {
        if (abs(x1 - x2) % a == 0) cout << "Yes\n";
        else cout << "No\n";
    } else {
        if ((x1 - x2) % a == 0 || (Y1 - y2) % b == 0) cout << "Yes\n";
        else cout << "No\n";
    }
}

signed main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}

5. Codeforces Round 1031 C. Smilo and Minecraft

题目链接:Codeforces Round 1031 C

题意:给出大小为 n × m n \times m n×m ( n , m ≤ 500 n, m \le 500 n,m500)的矿区,每一个格子可以是金矿 ( g g g)、石头 ( # \# #) 或者是空地 ( . . .)。收集金矿的方法为在一个空地安放炸药,以炸药为中心边长为 2 k + 1 2k + 1 2k+1 ( k ≤ 500 k \le 500 k500) 的正方形内,最边缘的金矿可以被收集,其余位置全部变为空地。问最多能收集多少金矿。

正确思路及代码

可以发现无论 k k k 等于多少,第二次操作开始一定不会损失金矿,所以第一次操作只需要选择损失金矿最少的空地进行操作即可。为了快速查询区间内的金矿数量,可以利用二维前缀和对金矿数量进行维护。

时间复杂度: O ( n m ) O(nm) O(nm)

#include 
using namespace std;

#define int long long

int n, m, k, pre[505][505];

void solve() {
	cin >> n >> m >> k;
	string s[505];
	for (int i = 1; i <= n; i++) cin >> s[i], s[i] = " " + s[i];
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= m; j++) pre[i][j] = 0;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + (s[i][j] == 'g');
	int res = INT_MAX;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (s[i][j] == '.') {
				int maxx = min(n, i + k - 1), maxy = min(m, j + k - 1);
				int minx = max(1LL, i - k + 1), miny = max(1LL, j - k + 1);
				int tmp = pre[maxx][maxy] - pre[minx - 1][maxy] - pre[maxx][miny - 1] + pre[minx - 1][miny - 1];
				res = min(res, tmp);
			}
	cout << pre[n][m] - res << "\n";
}

signed main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}

2025.6.17

1. 洛谷 P1126 机器人搬重物

题目链接:洛谷 P1226

题意:机器人是一个直径为 1.6 1.6 1.6 米的求,在一个 N × M N \times M N×M 的网格上,有的格子为不可移动的障碍。机器人可以接受向前 1 1 1 步、向前 2 2 2 步、向前 3 3 3 步、左转、右转五个指令,执行每个指令需要 1 1 1 秒,问从起点走到终点最少要多少秒。

正确思路及代码

将到达所有点的时间初始化为无穷大,利用 b f s bfs bfs 找出依次处理出到每一个点的最小时间(每当找到到达一个点的时间比之前的小,依次更新往后的所有节点,类似于图论中的松弛操作)。

时间复杂度: O ( n 2 m 2 ) O(n^2m^2) O(n2m2)

#include 
using namespace std;

const int dx[4] = { 0, 1, 0, -1 }, dy[4] = { 1, 0, -1, 0 };
unordered_map<char, int> mp = { {'E', 0}, {'S', 1}, {'W', 2}, {'N', 3}};
int n, m, sx, sy, tx, ty, a[55][55], ans[55][55], vis[55][55], dir;

void bfs() {
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= m; j++) ans[i][j] = INT_MAX, vis[i][j] = 0;
	queue<tuple<int, int, int>> q;
	q.push({dir, sx, sy});
	ans[sx][sy] = 0;
	while (!q.empty()) {
		int d = get<0>(q.front()), x = get<1>(q.front()), y = get<2>(q.front());
		q.pop();
		for (int i = 0; i < 4; i++) {
			for (int j = 1; j <= 3; j++) {
				int nx = x + dx[i] * j, ny = y + dy[i] * j;
				if (nx <= 0 || nx >= n || ny <= 0 || ny >= m || a[nx][ny]) break;
				int time = min((d - i + 4) % 4, (i - d + 4) % 4) + 1;
				if (ans[nx][ny] > ans[x][y] + time) {
					q.push({i, nx, ny});
					ans[nx][ny] = ans[x][y] + time;
				}
			}
		}
	}
}

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			cin >> a[i][j];
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (a[i][j]) a[i - 1][j] = a[i][j - 1] = a[i - 1][j - 1] = a[i][j];
	char c;
	cin >> sx >> sy >> tx >> ty >> c;
	dir = mp[c];
	bfs();
	if (ans[tx][ty] < INT_MAX) cout << ans[tx][ty] << endl;
	else cout << -1 << endl;
	return 0;
}

2025.6.20

1. 洛谷 P1198 [JSOI2008] 最大数

题目链接:洛谷 P1198

正确思路及代码

线段树模板题,单点修改 + 区间查询。

时间复杂度: O ( M log ⁡ l e n ) O(M\log len) O(Mloglen)

#include 
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)

const int N = 4e5 + 5;
int m, d, t[N << 2], len = 0, ans = 0;

void insert(int q, int k, int p = 1, int l = 1, int r = N - 1) {
    if (l == r) {
        t[p] = k % d;
        return;
    }
    int mid = (l + r) >> 1;
    if (q <= mid) insert(q, k, ls, l, mid);
    else insert(q, k, rs, mid + 1, r);
    t[p] = max(t[ls], t[rs]);
}

int query(int ql, int qr, int p = 1, int l = 1, int r = N - 1) {
    if (ql <= l && r <= qr) return t[p];
    int mid = (l + r) >> 1, oup = 0;
    if (ql <= mid) oup = max(oup, query(ql, qr, ls, l, mid));
    if (qr > mid) oup = max(oup, query(ql, qr, rs, mid + 1, r));
    return oup;
}

signed main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    cin >> m >> d;
    while (m--) {
        char op; int num;
        cin >> op >> num;
        if (op == 'Q') {
            ans = query(len - num + 1, len);
            cout << ans << "\n";
        }
        if (op == 'A') {
            insert(++len, ans + num);
        }
    }
    return 0;
}

2. P1314 [NOIP 2011 提高组] 聪明的质监员

题目链接:洛谷 P1314

错误思路及代码

错误原因:二分边界处理错误。

#include 
using namespace std;

#define int long long

const int N = 2e5 + 5;
int n, m, s, w[N], v[N], prea[N], preb[N], L[N], R[N];

int cal(int W) {
	memset(prea, 0, sizeof(prea)), memset(preb, 0, sizeof(preb));
    for (int i = 1; i <= n; i++) {
        prea[i] = prea[i - 1], preb[i] = preb[i - 1];
        if (w[i] >= W) prea[i]++, preb[i] += v[i];
    }
    int sum = 0;
    for (int i = 1; i <= m; i++)
        sum += (prea[R[i]] - prea[L[i] - 1]) * (preb[R[i]] - preb[L[i] - 1]);
    return sum;
}

signed main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    cin >> n >> m >> s;
    for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
    for (int i = 1; i <= m; i++) cin >> L[i] >> R[i];
    int l = 0, r = 2e6 + 10;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (cal(mid) <= s) r = mid;
        else l = mid + 1;
    }
    cout << min(abs(s - cal(r)), abs(s - cal(r + 1))) << endl;
    return 0;
}

正确思路及代码

这道题目可以发现以下性质

  • 当选取的 W W W 越大, y y y 就越小,所以可以二分 W W W,找出最接近 s s s y y y 所对应的 W W W 的取值。
  • 对于给定 W W W,如何求 y y y 的值?
    • a i a_i ai 表示当前情况下第 i i i 个矿石是否 ( 0 / 1 0/1 0/1) 被记入 y y y 中,对 a i a_i ai b i = a i × v i b_i = a_i \times v_i bi=ai×vi 维护一段前缀和。
    • 对于每一个区间 [ l , r ] [l, r] [l,r] 的查询,结果为 a [ l , r ] × b [ l , r ] a[l, r] \times b[l, r] a[l,r]×b[l,r]

时间复杂度: O ( ( n + m ) log ⁡ W ) O((n + m)\log W) O((n+m)logW)

#include 
using namespace std;

#define int long long

const int N = 2e5 + 5;
int n, m, s, w[N], v[N], prea[N], preb[N], L[N], R[N];

int cal(int W) {
	memset(prea, 0, sizeof(prea)), memset(preb, 0, sizeof(preb));
    for (int i = 1; i <= n; i++) {
        prea[i] = prea[i - 1], preb[i] = preb[i - 1];
        if (w[i] >= W) prea[i]++, preb[i] += v[i];
    }
    int sum = 0;
    for (int i = 1; i <= m; i++)
        sum += (prea[R[i]] - prea[L[i] - 1]) * (preb[R[i]] - preb[L[i] - 1]);
    return sum;
}

signed main() {
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    cin >> n >> m >> s;
    for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
    for (int i = 1; i <= m; i++) cin >> L[i] >> R[i];
    int l = 0, r = 1e6 + 1;
    while (l + 1 < r) {
        int mid = (l + r) >> 1;
        if (cal(mid) <= s) r = mid;
        else l = mid;
    }
    cout << min(abs(s - cal(r)), abs(s - cal(l))) << endl;
    return 0;
}

2025.6.21

1. 洛谷 P1343 地震逃生

题目链接:洛谷 P1343

题意:给出一个 n n n 个点 m m m 条边的带权有向图,边权为最大学生容纳量,学生要从 1 1 1 号节点逃跑到 n n n 号节点,问每一批最多能运送多少学生,最少需要多少批才能运送完全部学生。

错误思路及代码

错误原因:没有开 long long

#include 
using namespace std;

const int N = 205, M = 2005;
int n, m, x, head[N], num = 1, d[N];
struct edge { int to, nxt, w; } e[M << 1];

void addEdge(int u, int v, int w) {
	e[++num] = (edge){ v, head[u], w }, head[u] = num;
}

bool bfs() {
	memset(d, 0, sizeof(d));
	queue<int> q;
	q.push(1);
	d[1] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (int i = head[u]; i; i = e[i].nxt) {
			int v = e[i].to, w = e[i].w;
			if (!d[v] && w) {
				d[v] = d[u] + 1;
				q.push(v);
			}
		}
	}
	return d[n];
}

int dinic(int cur, int flow) {
	if (cur == n) return flow;
	int res = flow;
	for (int i = head[cur]; i; i = e[i].nxt) {
		if (!res) return flow;
		int v = e[i].to, w = e[i].w;
		if (!w) continue;
		if (d[v] != d[cur] + 1) continue;
		int k = dinic(v, min(res, w));
		if (!k) d[v] = 0;
		res -= k, e[i].w -= k, e[i ^ 1].w += k;
	}
	return flow - res;
}

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> m >> x;
	for (int i = 1; i <= m; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		addEdge(a, b, c), addEdge(b, a, 0);
	}
	int maxFlow = 0, flow = 0;
	while (bfs()) {
		while (flow = dinic(1, INT_MAX)) maxFlow += flow;
	}
	if (!maxFlow) cout << "Orz Ni Jinan Saint Cow!" << endl;
	else cout << maxFlow << " " << (x + maxFlow - 1) / maxFlow << endl;
	return 0;
}

正确思路及代码

最大流模板题,利用 dinic 算法算出从起点 1 1 1 到终点 n n n 的最大流,这个数值 maxFlow 就是每批能运出的最多学生数量,所以运送学生最少需要 ⌈ x m a x F l o w ⌉ = ⌊ m a x F l o w + x − 1 m a x F l o w ⌋ \lceil \frac{x}{maxFlow} \rceil = \lfloor \frac{maxFlow + x - 1}{maxFlow} \rfloor maxFlowx=maxFlowmaxFlow+x1 批。当 m a x F l o w = 0 maxFlow = 0 maxFlow=0 时无法到达目的地,输出 Orz Ni Jinan Saint Cow!

时间复杂度: O ( n 2 m ) O(n^2m) O(n2m)

#include 
using namespace std;

#define int long long

const int N = 205, M = 2005;
int n, m, x, head[N], num = 1, d[N];
struct edge { int to, nxt, w; } e[M << 1];

void addEdge(int u, int v, int w) {
	e[++num] = (edge){ v, head[u], w }, head[u] = num;
}

bool bfs() {
	memset(d, 0, sizeof(d));
	queue<int> q;
	q.push(1);
	d[1] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (int i = head[u]; i; i = e[i].nxt) {
			int v = e[i].to, w = e[i].w;
			if (!d[v] && w) {
				d[v] = d[u] + 1;
				q.push(v);
			}
		}
	}
	return d[n];
}

int dinic(int cur, int flow) {
	if (cur == n) return flow;
	int res = flow;
	for (int i = head[cur]; i; i = e[i].nxt) {
		if (!res) return flow;
		int v = e[i].to, w = e[i].w;
		if (!w) continue;
		if (d[v] != d[cur] + 1) continue;
		int k = dinic(v, min(res, w));
		if (!k) d[v] = 0;
		res -= k, e[i].w -= k, e[i ^ 1].w += k;
	}
	return flow - res;
}

signed main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> m >> x;
	for (int i = 1; i <= m; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		addEdge(a, b, c), addEdge(b, a, 0);
	}
	int maxFlow = 0, flow = 0;
	while (bfs()) {
		while (flow = dinic(1, LLONG_MAX)) maxFlow += flow;
	}
	if (!maxFlow) cout << "Orz Ni Jinan Saint Cow!" << endl;
	else cout << maxFlow << " " << (x + maxFlow - 1) / maxFlow << endl;
	return 0;
}

2. 洛谷 P1792 [国家集训队] 种树

题目链接:洛谷 P1792

题意:给出 n n n 个环形的种树位置,编号按顺时针从 1 1 1 n n n,每一个位置种树后会得到 A i A_i Ai 的美观度,总共要种 m m m 棵树,求最大美观度,如果没有合法的种树方案,输出 Error!

正确思路及代码

反悔贪心 + 双向链表。

不合法情况: m > n 2 m > \frac{n}{2} m>2n,因为每两棵树之间之隔一个位置是能种最多树的方案,总共能种 n 2 \frac{n}{2} 2n 棵树。

要求最大美观度肯定是要尽量多的选择剩余种树位置中美观度最大的位置去种树,但是由于存在相邻位置每次都选择当前最大美观度的位置种树不一定最优,所以要加入一个反悔操作。

例如,对于下面这组样例

4 2
1 6 7 6

答案是 12 12 12,但是如果每一次都是选择剩余可以种树位置的最大美观度的位置去种树的话,算出来的答案就会是 1 + 7 = 8 1 + 7 = 8 1+7=8

如何加入反悔操作?

可以发现,假如按照每一次选一个种树位置的方式去选 m m m 个种树位置,如果要进行反悔,肯定是要将已经选择的某个位置 i i i 不选,改为同时选择这个位置旁边两个位置 l i l_i li r i r_i ri l i l_i li r i r_i ri 是当前第 i i i 个位置左右相邻的种树位置。对于上面的样例,如果第一轮选择了 7 7 7,第二轮的反悔实际上就是不选 7 7 7 改为选旁边的两个 6 6 6

这样的反悔操作,实际上是让答案增大了 a l i + a r i − a i a_{l_i} + a_{r_i} - a_i ali+ariai。所以每一次操作之后我们只需要将 i i i l i l_i li r i r_i ri 这三个种树位置种删除,再在这个位置插入一个美观度为 a l i + a r i − a i a_{l_i} + a_{r_i} - a_i ali+ariai 的种树位置即可。

时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include 
using namespace std;

const int N = 4e5 + 5;
int n, m, a[N], l[N], r[N], vis[N];
priority_queue<pair<int, int>> q;

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		l[i] = i - 1, r[i] = i + 1;
		q.push({a[i], i});
	}
	r[n] = 1, l[1] = n;
	if (m > n / 2) {
		cout << "Error!" << endl;
		return 0;
	}
	memset(vis, 0, sizeof(vis));
	int ans = 0, idx = n;
	for (int i = 1; i <= m && !q.empty(); i++) {
		while (!q.empty() && vis[q.top().second]) q.pop();
		int val = q.top().first, pos = q.top().second;
		ans += val;
		vis[pos] = vis[l[pos]] = vis[r[pos]] = 1;
		a[++idx] = a[l[pos]] + a[r[pos]] - a[pos];
		l[idx] = l[l[pos]], r[idx] = r[r[pos]];
		r[l[l[pos]]] = idx, l[r[r[pos]]] = idx;
		q.push({a[idx], idx});
	}
	cout << ans << endl;
	return 0;
}

3. 洛谷 P1903 [国家集训队] 数颜色 / 维护队列

题目链接:洛谷 P1903

题意:给出 n n n 支彩笔,每一支彩笔用一个整数代表其颜色。进行 m m m 次操作,Q L R 表示询问区间 [ L , R ] [L, R] [L,R] 内总共有多少支彩笔,R P C 表示将第 P P P 支彩笔的颜色改为 C C C

正确思路及代码

离线操作,带修改的莫队模板题。将所有操作读入后,给所有询问操作记录一个修改时间,表示是在多少次修改后进行的询问。按照每次询问左端点、右端点所在的块以及时间分别为第一、二、三关键字进行排序,接着用莫队找出答案。

时间复杂度: O ( n 2 3 m 2 3 t 1 3 ) O(n^{\frac{2}{3}}m^{\frac{2}{3}}t^{\frac{1}{3}}) O(n32m32t31)

#include 
using namespace std;

const int N = 133335, M = 1e6 + 5;
int n, m, a[N], vis[M], tot = 0, cntq = 0, cntu = 0, ans[N], p[N];
struct query { int l, r, t; } q[N];
struct update { int x, y; } u[N];

void add(int x) {
	if (!vis[x]) tot++;
	vis[x]++;
}

void del(int x) {
	vis[x]--;
	if (!vis[x]) tot--;
}

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> m;
	int len = pow(n, 2.0 / 3.0);
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= m; i++) {
		char c; int x, y;
		cin >> c >> x >> y;
		if (c == 'Q') q[++cntq] = (query){ x, y, cntu };
		if (c == 'R') u[++cntu] = (update){ x, y };
		p[i] = i;
	}
	sort(p + 1, p + cntq + 1, [&](int x, int y){
		if (q[x].l / len == q[y].l / len) {
			if (q[x].r / len == q[y].r / len) return q[x].t < q[y].t;
			return q[x].r / len < q[y].r / len;
		}
		return q[x].l / len < q[y].l / len;
	});
	int L = 1, R = 0, T = 0;
	for (int i = 1; i <= cntq; i++) {
		while (L < q[p[i]].l) del(a[L++]);
		while (R > q[p[i]].r) del(a[R--]);
		while (L > q[p[i]].l) add(a[--L]);
		while (R < q[p[i]].r) add(a[++R]);
		while (T < q[p[i]].t) {
			T++;
			if (u[T].x >= L && u[T].x <= R) {
				add(u[T].y);
				del(a[u[T].x]);
			}
			swap(a[u[T].x], u[T].y);
		}
		while (T > q[p[i]].t) {
			if (u[T].x >= L && u[T].x <= R) {
				add(u[T].y);
				del(a[u[T].x]);
			}
			swap(a[u[T].x], u[T].y);
			T--;
		}
		ans[p[i]] = tot;
	}
	for (int i = 1; i <= cntq; i++) cout << ans[i] << endl;
	return 0;
}

4. 洛谷 P1575 正误问题

题目链接:洛谷 P1575

题意:给出一个只含 truefalseorandnot 和空格的字符串表达式,判断这个字符串表达式最后的结果是 true 还是 false,如果表达式不合法则输出 error

正确思路及代码

按照题意模拟。

#include 
using namespace std;

unordered_map<string, int> mp = {{"true", 1}, {"false", 0}};

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	string s, str;
	stack<string> op;
	stack<int> num;
	int flag = 0, tag = 0;
	getline(cin, s);
	s += " ";
	for (int i = 0; i < (int)s.size(); i++) {
		if (s[i] == ' ') {
			if (str == "and" || str == "or") {
				if (flag || num.size() != op.size() + 1) {
					cout << "error" << endl;
					return 0;
				}
				op.push(str);
				tag |= 1;
			} else if (str == "not") {
				flag ^= 1;
				tag |= 2;
			} else {
				if (flag) {
					if (str == "true") str = "false";
					else str = "true";
					flag = 0;
				}
				if (num.size() != op.size()) {
					cout << "error" << endl;
					return 0;
				}
				int val = mp[str];
				while (!num.empty() && !op.empty() && op.top() == "and") {
					val &= num.top();
					op.pop(), num.pop();
				}
				num.push(val);
				tag |= 4;
			}
			str = "";
		} else str += s[i];
	}
	if (flag || ((tag & 4) == 0)) {
		cout << "error" << endl;
		return 0;
	}
	int ans = 0;
	while (!num.empty()) ans |= num.top(), num.pop();
	if (ans) cout << "true" << endl;
	else cout << "false" << endl;
	return 0;
}

2025.6.24

1. 洛谷 P2389 电脑班的裁员

题目链接:洛谷 P2389

题意:给出 n n n ( n ≤ 500 n \le 500 n500) 个 a i a_i ai ( − 1000 ≤ a i ≤ 1000 -1000 \le a_i \le 1000 1000ai1000),选出不超过 k k k ( k ≤ n k \le n kn) 段 a i a_i ai,使得其总和最大。

正确思路及代码

d p i , j , 0 / 1 dp_{i,j,0/1} dpi,j,0/1 表示考虑到第 i i i 位,已经选择了 j j j 段连续 a i a_i ai,选/不选第 i i i 位的总和的最大值。

  • j = 0 j = 0 j=0 时, d p i , j , 0 = d p i − 1 , j , 0 dp_{i, j, 0} = dp_{i - 1, j, 0} dpi,j,0=dpi1,j,0
  • j > 0 j > 0 j>0 时, d p i , j , 0 = max ⁡ ( d p i − 1 , j , 0 , d p i − 1 , j , 1 ) dp_{i,j,0} = \max(dp_{i -1,j,0}, dp_{i - 1,j,1}) dpi,j,0=max(dpi1,j,0,dpi1,j,1), d p i , j , 1 = max ⁡ ( d p i − 1 , j , 1 , d p i − 1 , j − 1 , 0 ) + a i dp_{i, j, 1} = \max(dp_{i - 1, j, 1}, dp_{i - 1, j - 1, 0}) + a_i dpi,j,1=max(dpi1,j,1,dpi1,j1,0)+ai

最后答案为 max ⁡ j = 0 k d p n , j , 0 / 1 \max_{j = 0}^k dp_{n, j, 0/1} maxj=0kdpn,j,0/1

#include 
using namespace std;

const int N = 505;
int n, k, dp[N][N][2], a[N];

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= k; j++) {
			if (!j) dp[i][j][0] = dp[i - 1][j][0], dp[i][j][1] = INT_MIN;
			else {
				dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1]);
				dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0]) + a[i];
			}
		}
	}
	int ans = 0;
	for (int j = 0; j <= k; j++) ans = max({ans, dp[n][j][0], dp[n][j][1]});
	cout << ans << endl;
	return 0;
}

2025.6.30

1. 洛谷 P1360 [USACO07MAR] Gold Balanced Lineup G

题目链接:洛谷 P1360

题意:给出 m m m 个能力, n n n 个数字,其二进制表示下的 1 1 1 表示对应能力得到提升。定义均衡时期为一段时间内每个能力都提升了相同的次数,问最大的均衡时期。

正确思路及代码

对于 1 ≤ i ≤ m 1 \le i \le m 1im,我们要找到一对 [ l , r ] [l, r] [l,r] 满足 p r e r , i − p r e l − 1 , i = k pre_{r, i} - pre_{l - 1, i} = k prer,iprel1,i=k ( k k k 为定值),即对于 1 < i ≤ m 1 < i \le m 1<im,满足 p r e r , i − p r e r , i − 1 = p r e l − 1 , i − p r e l − 1 , i pre_{r, i} - pre_{r, i - 1} = pre_{l - 1, i} - pre_{l - 1, i} prer,iprer,i1=prel1,iprel1,i。所以对于每一个位置 j j j,我们只需要找出前面某一个位置 k k k 满足 p r e j , i − p r e j , i − 1 = p r e k , i − p r e k , i − 1 pre_{j, i} - pre_{j, i - 1} = pre_{k, i} - pre_{k, i - 1} prej,iprej,i1=prek,iprek,i1 即可。

时间复杂度: O ( n m log ⁡ n ) O(nm\log n) O(nmlogn)

#include 
using namespace std;

const int N = 1e5 + 5;
int n, m, ans = 0;
map<vector<int>, int> mp;

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> m;
	vector<int> pre(m, 0);
	mp[pre] = 0;
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> x;
		for (int j = m - 1; j >= 0; j--)
			if (x & (1LL << j)) pre[j]++;
		vector<int> now(m);
		for (int j = 1; j < m; j++) now[j] = pre[j] - pre[j - 1];
		if (mp.count(now)) ans = max(ans, i - mp[now]);
		else mp[now] = i;
	}
	cout << ans << "\n";
	return 0;
}

2. 洛谷 P1472 [USACO2.3] 奶牛家谱 Cow Pedigrees

题目链接:洛谷 P1472

题意:一个有 n n n 个节点,深度为 k k k 的无标号完满二叉树(即每个节点的儿子数为 0 0 0 2 2 2)有多少种结构?定义根节点深度为 1 1 1,答案对 9901 9901 9901 取模。

正确思路及代码

  • d p i , j dp_{i, j} dpi,j 为有 i i i 个节点,深度小于等于 j j j 的无标号完满二叉树的结构数量。
  • 一个无标号完满二叉树可以看作由一个根节点及左右两棵子树组成,因此状态转移方程为 d p i , j = ∑ t = 1 i − 1 d p t , j − 1 × d p i − t − 1 , j − 1 dp_{i, j} = \sum_{t = 1}^{i - 1} dp_{t, j - 1} \times dp_{i - t - 1, j - 1} dpi,j=t=1i1dpt,j1×dpit1,j1,最后答案为 d p n , k − d p n , k − 1 dp_{n, k} - dp_{n, k - 1} dpn,kdpn,k1
  • 注意:每一个无标号完满二叉树的节点数只可能是奇数,所以枚举节点数量时,只能枚举奇数。

时间复杂度: O ( n 2 k ) O(n^2k) O(n2k)

#include 
using namespace std;

const int mod = 9901;
int n, k, dp[205][105];

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> k;
	for (int j = 1; j <= k; j++) dp[1][j] = 1;
	for (int j = 1; j <= k; j++)
		for (int i = 3; i <= n; i += 2)
			for (int t = 1; t <= i - 1; t += 2)
				dp[i][j] = (dp[i][j] + dp[t][j - 1] * dp[i - t - 1][j - 1] % mod) % mod;
	cout << (dp[n][k] - dp[n][k - 1] + mod) % mod << endl;
	return 0;
}

3. P1545 [USACO04DEC] Dividing the Path G

题目链接:洛谷 P1545

正确思路及代码

DP + 线段树优化。

  • 为了使每一个奶牛的草区 [ s , e ] [s, e] [s,e] 只被一个喷灌器覆盖,区间 [ s + 1 , e − 1 ] [s + 1, e - 1] [s+1,e1] 内的所有点不能作为喷灌器射程端点,可以利用一个数组来打上标记。由于暴力处理的时间复杂度是 O ( N L ) O(NL) O(NL),时间上无法接受,因此要利用差分把时间复杂度优化到 O ( N ) O(N) O(N)
  • 由于喷灌器的射程为整数,因此喷灌器射程端点只能是偶数。
  • d p i dp_i dpi 表示覆盖 [ 0 , i ] [0, i] [0,i] 最少需要的线段数量,可以发现 d p i = min ⁡ ( d p j ) + 1 dp_i = \min(dp_j) + 1 dpi=min(dpj)+1,其中 j ∈ [ i − 2 a , i − 2 b ] j \in [i - 2a, i - 2b] j[i2a,i2b]。暴力转移的时间复杂度为 O ( L 2 ) O(L^2) O(L2),利用线段树可以将复杂度优化到 O ( L log ⁡ L ) O(L\log L) O(LlogL)

时间复杂度: O ( N + L log ⁡ L ) O(N + L\log L) O(N+LlogL)

#include 
using namespace std;

#define ls (p << 1)
#define rs (p << 1 | 1)
#define int long long

const int L = 1e6 + 5;
int n, len, a, b, dp[L], vis[L], t[L << 3];

void build(int p = 1, int l = 0, int r = len) {
	if (l == r) {
		t[p] = INT_MAX;
		return;
	}
	int mid = (l + r) >> 1;
	build(ls, l, mid), build(rs, mid + 1, r);
	t[p] = min(t[ls], t[rs]);
}

void modify(int q, int k, int p = 1, int l = 0, int r = len) {
	if (l == r) {
		t[p] = k;
		return;
	}
	int mid = (l + r) >> 1;
	if (q <= mid) modify(q, k, ls, l, mid);
	else modify(q, k, rs, mid + 1, r);
	t[p] = min(t[ls], t[rs]);
}

int query(int ql, int qr, int p = 1, int l = 0, int r = len) {
	if (ql <= l && r <= qr) return t[p];
	int mid = (l + r) >> 1, ans = INT_MAX;
	if (ql <= mid) ans = min(ans, query(ql, qr, ls, l, mid));
	if (qr > mid) ans = min(ans, query(ql, qr, rs, mid + 1, r));
	return ans;
}

signed main() {
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> len >> a >> b;
	for (int i = 0; i <= len; i++) dp[i] = 0;
	for (int i = 1; i <= n; i++) {
		int s, e;
		cin >> s >> e;
		vis[s + 1]++, vis[e]--;
	}
	for (int i = 1; i <= len; i++) vis[i] += vis[i - 1];
	build();
	modify(0, 0);
	for (int i = 2 * a; i <= len; i += 2) {
		if (vis[i]) continue;
		int ql = max(0LL, i - 2 * b), qr = i - 2 * a;
		dp[i] = query(ql, qr) + 1;
		modify(i, dp[i]);
	}
	if (dp[len] >= INT_MAX) dp[len] = -1;
	cout << dp[len] << endl;
	return 0;
}

你可能感兴趣的:(Data,Structure,and,Algorithms,算法)