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 \leq i < n\) 并交换值 \(a_i\) 和 \(a_{i + 1}\) 。
- 选择索引 \(1 \leq i < n\) 并交换值 \(b_i\) 和 \(b_{i + 1}\) 。
- 选择索引 \(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\) 的字符串,考虑这些字符串的贡献如何计算
假设上一位的状态为蓝色笔画的三角形,当前位新加入了一个 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 的个数加 1add (zero + 1, 1);} else {sum += query (1, zero);sum++;zero++;//c0 - c1 = -1 的个数加 1add (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]\)
看着这张图应该就能理解了
#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;
}