4 CF 1032 div3 题解

31 CF 1032 div3 题解

C

题面

给你一个行数为 \(n\) 列数为 \(m\) 的整数矩阵。在第 \(i\) 行和第 \(j\) 列的交叉处的单元格中包含数字 \(a_{ij}\)

您可以执行以下操作

  • 选择两个数字 \(1 \leq r \leq n\)\(1 \leq c \leq m\)
  • 对于矩阵中所有单元格 \((i, j)\) 中的 \(i = r\)\(j = c\) ,将 \(a_{ij}-1\)

找到矩阵 \(a\) 中进行一次此类运算后的最小最大值。

\(n\cdot m \le 10^5\)

题解

这题可以可以先预处理出每一行和每一列的最大值,然后枚举 \(r, c\) 计算 \(r,c\) 包含的最大值数量是否等于最大值总数量,如果能够全部覆盖,\(ans=ma - 1\) ,否则 \(ans = ma\)

D

题面

给你两个整数数组 \(a_1, a_2, \ldots, a_n\)\(b_1, b_2, \ldots, b_n\) 。保证从 \(1\)\(2 \cdot n\) 的每个整数都正好出现在其中一个数组中。

您需要执行一定数量的操作(可能为零),以满足以下两个***条件:

  • 对于每个 \(1 \le i <n\)\(a_i < a_{i+1}\)\(b_i < b_{i+1}\) 都成立。
  • 对于每一个 \(1 \leq i \leq n\)\(a_i < b_i\) 成立。

在每个操作中,您可以执行以下三个操作中的一个:

  1. 选择索引 \(1 \leq i < n\) 并交换值 \(a_i\)\(a_{i + 1}\)
  2. 选择索引 \(1 \leq i < n\) 并交换值 \(b_i\)\(b_{i + 1}\)
  3. 选择索引 \(1 \leq i \leq n\) ,交换数值 \(a_i\)\(b_i\)

你不需要尽量减少操作次数,但总次数不能超过 \(1709\) 。找出满足两个条件的任意序列

题解

当时以为求最小操作次数,对着样例疯狂导管,后来发现题解也过不了样例

这题可以先分别进行冒泡排序,然后看 \(a_i,b_i\) 的大小,如果 \(a_i > b_i\) ,交换

先看看为什么这样是对的

假设我们现在 \(a_i > b_i\)\(a_{i+1} > a_i \ ,\ b_{i+1} > b_i\)

  • 因为 \(a_i > b_i \ , \ a_i < a_{i+1}\) 所以 \(b_i < a_{i+1}\) ,不会产生冲突
  • 考虑 \(a_i\)\(b_{i + 1}\) 是否会产生冲突
    • 如果 \(a_{i+1} > b_{i+1}\) 那么他俩也会交换,所以 \(a_i < a_{i+1}\) ,仍然满足条件
    • 否则 \(a_{i+1} <= b_{i+1}\) ,所以 \(a_i < b_{i+1}\) ,满足条件

再看这样操作的最极限的操作数 \(n^2\) 是怎么来的

  • 首先,冒泡排序,\(\displaystyle n-1+n-2+...+1 = \frac{n(n-1)}{2}\) ,两遍 \(n(n-1)\)

  • 如果每个位置都需要换 \(n\)

最终的 \(ans = n(n-1) + n = n^2\)

E

题面

对于两个整数 \(a\)\(b\) ,我们定义 \(f(a, b)\) 为数字 \(a\)\(b\) 的十进制表示中相同数字个数。例如, \(f(12, 21) = 0\)\(f(31, 37) = 1\)\(f(19891, 18981) = 2\)\(f(54321, 24361) = 3\)

给你两个长度相同的十进制整数 \(l\)\(r\) 。考虑所有整数 \(l \leq x \leq r\) 。你的任务是找出 \(f(l, x) + f(x, r)\) 的最小值。

\(1 \le l \le r \le 10^9\)

题解

可以用数位 dp 无脑写,可惜考场上没开到

放下代码,后来写的时候还是想了一会,有下界的数位 dp

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>

using namespace std;

const int N = 15;
int a[N], b[N];
int f[N][N << 1];
//f[pos][sum]:[pos + 1, len] 填完,已经有 sum 位和 l, r 相同,剩下位随便填的最小相同位数
int dfs (int pos, int sum, bool limu, bool limd) {
	if (!pos) return sum;
	auto &now = f[pos][sum];
	if (!limu && !limd && ~now) return now;
	int res = 30;
	int down = limd ? a[pos] : 0;
	int up = limu ? b[pos] : 9;
	for (int i = down; i <= up; i++) {
		int s = sum + (i == a[pos]) + (i == b[pos]);
		res = min (res, dfs (pos - 1, s, limu && (i == up), limd && (i == down)));
	}
	if (!limd && !limu) now = res;
	return res;
}
int solve (int l, int r) {
	int len = 0;
	while (l) {
		a[++len] = l % 10;
		l /= 10;
	}
	len = 0;
	while (r) {
		b[++len] = r % 10;
		r /= 10;
	}
	return dfs (len, 0, 1, 1);
}

int main () {
	int T;
	cin >> T;
	while (T--) {
		memset (f, -1, sizeof f);
		int l, r;
		cin >> l >> r;
		cout << solve (l, r) << endl;
    }
	return 0;
}

F

题面

给你一个由整数 \(a_1, a_2, \ldots, a_n\) 和两个整数 \(s\)\(x\) 组成的数组。请计算元素之和等于 \(s\) 且最大值等于 \(x\) 的数组子段的数目。

更具体地说,计算有多少对 \(1 \leq l \leq r \leq n\) 这样的数组:

  • \(a_l + a_{l + 1} + \ldots + a_r = s\) .
  • \(\max(a_l, a_{l + 1}, \ldots, a_r) = x\) .

\(1 \leq n \leq 2 \cdot 10^5\)

\(-10^9 \leq a_i \leq 10^9\)

题解

题目中的 \(\sum\) 很好求,可以用前缀和求,区间最大值我想到用st表求,但如何枚举 \(l, r\) 难住了我,因为无论怎样,我都需要枚举 \(l, r\) 时间复杂度为 \(O(n^2)\)

后来看题解,题解摒弃了枚举 \(l, r\) 的解法,而是枚举每个数,将 \(sum[l]\) 存入 \(map\) ,然后对于每个位置,找到它左边符合条件的左端点,计入答案,时间复杂度 \(O(n\log n)\) ,尝试用 \(map\) ,但被卡了

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <map>
using namespace std;

typedef long long ll;

const int N = 2e5 + 10;

int a[N];
ll sum[N];

int main () {
	int T, n, ma;
	ll s;
	cin >> T;
	while (T--) {
		cin >> n >> s >> ma;
		for (int i = 1; i <= n; i++)
			cin >> a[i], sum[i] = sum[i - 1] + a[i];
		map <ll, int> cnt;
		ll ans = 0;
		int l = 1;
		for (int i = 1; i <= n; i++) {
			if (a[i] > ma) {
				cnt.clear();
				l = i + 1;
			} else if (a[i] == ma) {
				while (l <= i) {
					cnt[sum[l - 1]]++;
					l++;
				}
			}
			ans += cnt[sum[i] - s];
		}
		cout << ans << endl;
	}
	return 0;
}

G

题面

给你一个长度为 \(n\) 的二进制字符串 \(s_1s_2 \ldots s_n\)

对于字符串 \(p\) ,我们将函数 \(f(p)\) 定义为字符串 \(p\) 中任意字符出现的最大次数。例如, \(f(00110) = 3\)\(f(01) = 1\)

你需要求出所有成对的 \(1 \leq l \leq r \leq n\) 的和 \(f(s_ls_{l+1} \ldots s_r)\)

题解

这道题确实不太好想,前缀和处理 \(0/1\) 出现的次数,然后暴力枚举 \(l,r\) ,除了 \(O(n^2)\) 做法,我真的想不出别的解法

题解的一种思路

从前向后枚举每一位,假设当前位为 \(i\) ,然后维护以 \(i\) 为右端点,长度为 \(1,2,...,i\) 的字符串,考虑这些字符串的贡献如何计算

image-20250626205209337

假设上一位的状态为蓝色笔画的三角形,当前位新加入了一个 0 ,那么我们新的一组字符串的贡献(\(val'\))实际上就是原来字符串的贡献(\(val\))加上新加的 0 会产生的贡献。

那么新加的 0 在什么时候会产生贡献?

可以发现,在原来串 \(C_0 >= C_1\) 的时候会产生 1 的贡献,那么我们就可以维护上个状态 \(C_0 - C_1\) 出现的次数,然后每次查询符合条件的字符串的个数,最后累加到最终的答案中即可,具体实现有些细节

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>


using namespace std;

typedef long long ll;

const int N = 2e5 + 15;

int T, n, m;
char s[N];

//树状数组单点修改,区间查询
int t[N << 1];
void add (int x, int d) {
	for (; x <= m; x += x & -x) {
		t[x] += d;
	}
}
int ask (int x) {
	int res = 0;
	for (; x; x -= x & -x) {
		res += t[x];
	}
	return res;
}
int query (int l, int r) {
	return ask(r) - ask(l - 1);
}

int main () {
	
	cin >> T;
	while (T--) {
		memset (t, 0, sizeof t);
		cin >> n;
		m = n * 2 + 20;
        //因为 c0 - c1 可能为负数,所以我们将 0 设为一个中间值,防止越界
		int zero = n + 10;
		scanf ("%s", s + 1);
		ll sum = 0, ans = 0;
		for (int i = 1; i <= n; i++) {
			if (s[i] == '0') {
                //找到 c0 - c1 >= 0 的字符串个数,属于额外贡献
				sum += query (zero, m - 1);
                //单独一个 0 的贡献
				sum++;
                //每个 c0 - c1 += 1 ,cnt-1 -> cnt0, cnt0 -> cnt1那么相当于将 0 向左移动一位
				zero--;
                //c0 - c1 = 1 的个数加 1
				add (zero + 1, 1);
			} else {
				sum += query (1, zero);
				sum++;
				zero++;
                //c0 - c1 = -1 的个数加 1
				add (zero - 1, 1);
			}
			ans += sum;
		}
		cout << ans << endl;
	}
	return 0;
}

H

题面

给你两个整数数组 \(l_1,l_2,...,l_n\)\(r_1,r_2,...,r_n\) 。求每个数组 \(1 \le k \le n\) 的解:

  • 考虑所有长度为 \(k\) 的整数数组 \(a\) ,使得每个 \(1 \leq i \leq k\) 都满足 \(l_i \le i \le r_i\) 。求所有满足条件的数组中最长的非递减子序列的最大长度。

题解

这题可以参照 \(LIS\)\(O(n \log n)\) 解法,维护一个 \(dp[i]\) 表示长度为 \(i\) 的不下降子序列的末尾的最小值

考虑新加一个 \(l_i, r_i\) 所产生的贡献,其实相当于直接更新了一个 \(l_i, r_i\) 区间,在 \(l_,r_i\) 内的每个数 \(x\) 都会更新 $ > x$ 的第一个 \(dp[j]\)

image-20250627074538004

看着这张图应该就能理解了

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>

using namespace std;

const int N = 2e5 + 10;

int T, n;

int main () {
    cin >> T;
    while (T--) {
        cin >> n;
        multiset <int> st;
        int l, r;
        for (int i = 1; i <= n; i++) {
            cin >> l >> r;
            st.insert(l);
            auto it = st.upper_bound(r);
            if (it != st.end()) {
                st.erase(it);
            }
            cout << st.size() << ' ';
        }
        cout << endl;
    }
    return 0;
}
posted @ 2025-10-10 21:12  michaele  阅读(6)  评论(0)    收藏  举报