牛客刷题-Day20
牛客刷题-Day20
今日刷题:\(1021-1030\)
1023 枚举 · 例20-【模板】子序列自动机Ⅰ|子序列检查

解题思路
枚举、二分。
预处理字符串 \(s\),记录每个出现的小写字母的个数以及出现的位置。
对于当前的字符串 \(t\),先进行特殊情况的判断,一是其长度不能大于 \(s\),二是其中的每个小写字母出现的次数不能大于 \(s\) 中对应字母出现的次数。
排除上述两种情况之后,对于当前的字符串 \(t\) 的一个字母 \(t_i\),其在字符串 \(s\) 中出现的位置要比其前一个字母出现的位置靠后,假设前一个字母 \(t_{i-1}\) 在 \(s\) 中出现的位置为 \(last\),则 \(t_i\) 的位置至少为 \(last+1\) 方才成立,在 \(t_i\) 对应的存储位置的数组中找到最小的不小于 \(last+1\) 的元素(符合要求的情况下尽量靠近前一个字母出现的位置),更新 \(last\) 为该值。
使用 \(last+1\) 可以避免相邻两个元素为同一个字母的情况,如果使用 \(last\) 作为比较对象,则查找到的可能会重合。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, M = 26;
int q;
string s;
vector<int> pos[M]; // pos[i] 存储字母 i 的位置 pos[i][0] 存储 i 的个数
int cnt[M];
int main() {
cin >> s;
for (int i = 0; i < M; i++)
pos[i].push_back(0);
for (int i = 0; s[i]; i++) { // 初始化
pos[s[i] - 'a'][0]++;
pos[s[i] - 'a'].push_back(i);
}
cin >> q;
while (q--) {
memset(cnt, 0, sizeof cnt);
string t;
cin >> t;
if (t.size() > s.size()) { // 比原串长
puts("NO");
continue;
}
bool flag = true;
for (int i = 0; t[i]; i++)
cnt[t[i] - 'a']++;
for (int i = 0; i < M; i++)
if (cnt[i] > pos[i][0]) { // t 中某个字母个数比 s 的多
flag = false;
break;
}
if (!flag) {
puts("NO");
continue;
}
int last = -1;
for (int i = 0; t[i]; i++) {
int l = 1, r = pos[t[i] - 'a'].size() - 1;
while (l < r) {
int mid = l + r >> 1;
if (pos[t[i] - 'a'][mid] >= last + 1)
r = mid;
else
l = mid + 1;
}
if (pos[t[i] - 'a'][l] >= last + 1)
last = pos[t[i] - 'a'][l];
else {
flag = false;
break;
}
}
if (!flag) {
puts("NO");
} else {
puts("YES");
}
}
return 0;
}
1024 贪心 · 例2-拼数

解题思路
排序。
重写一下排序函数:
- 从高位往低位比较,大的在前面;
- 其中一个为另一个的前缀子串,则比较 \(a+b\) 和 \(b+a\) 的大小。
对于第二种情况,举个栗子:\(51\) 和 \(510\),那么拼接要么是 \(51510\) 或者 \(51051\)。
bool cmp(string a, string b) {
for (int i = 0; i < a.size() && i < b.size(); i++) {
if (a[i] != b[i])
return a[i] > b[i];
}
return a + b > b + a;
}
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int n, a[N];
string s[N];
bool cmp(string a, string b) {
for (int i = 0; i < a.size() && i < b.size(); i++) {
if (a[i] != b[i])
return a[i] > b[i];
}
return a + b > b + a;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = to_string(a[i]);
}
sort(s + 1, s + n + 1, cmp);
for (int i = 1; i <= n; i++)
cout << s[i];
return 0;
}
1025 贪心 · 例3-给定长度和数位和

解题思路
贪心。
首先对特殊情况进行特判。然后,如果是最小的,则从后往前确定,末尾数字尽可能为 \(9\),不够 \(9\),则当前位置为剩下的 \(s-1\),最高位为 \(1\),因为不能存在前导零。如果是最大的,从前往后确定,前面的数字尽可能为 \(9\),不够则当前位置为剩下的 \(s\),后续位置为 \(0\) 即可。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int T;
int num[N];
int main() {
cin >> T;
while (T--) {
int m, s;
cin >> m >> s;
if (s > m * 9 || (s == 0 && m > 1))
cout << "-1" << endl;
else if (s == 0 && m == 1)
cout << "0 0" << endl;
else {
int len = m, tot = s;
while (tot > 0 && len >= 1) {
if (tot > 9) num[len--] = 9, tot = tot - 9;
else {
num[len] = tot - 1, num[1]++;
break;
}
}
for (int i = 1; i <= m; i++)
cout << num[i], num[i] = 0;
cout << ' ';
len = 1, tot = s;
while (tot > 0 && len <= m) {
if (tot > 9) num[len++] = 9, tot = tot - 9;
else num[len++] = tot, tot = 0;
}
for (int i = 1; i <= m; i++)
cout << num[i], num[i] = 0;
cout << endl;
}
}
return 0;
}
1026 贪心 · 例4-排座椅

解题思路
取最多的行和列进行分隔。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m, k, l, d;
struct Node {
int idx, cnt;
} row[N], col[N];
bool cmp1(Node a, Node b) {
return a.cnt > b.cnt;
}
bool cmp2(Node a, Node b) {
return a.idx < b.idx;
}
int main() {
scanf("%d%d%d%d%d", &n, &m, &k, &l, &d);
for (int i = 1; i <= n; i++)
row[i].idx = i;
for (int i = 1; i <= m; i++)
col[i].idx = i;
while (d--) {
int x, y, p, q;
scanf("%d%d%d%d", &x, &y, &p, &q);
if (x == p) col[min(y, q)].cnt++; // 左右相邻
else row[min(x, p)].cnt++; // 上下相邻
}
sort(row + 1, row + n + 1, cmp1);
sort(col + 1, col + m + 1, cmp1);
sort(row + 1, row + k + 1, cmp2);
sort(col + 1, col + l + 1, cmp2);
for (int i = 1; i <= k; i++)
printf("%d ", row[i].idx);
printf("\n");
for (int i = 1; i <= l; i++)
printf("%d ", col[i].idx);
printf("\n");
return 0;
}
1027 贪心 · 例5-矩阵消除游戏

解题思路
最初的想法是先求每行、每列的和,然后寻找最大的行或者列,删除之后重新计算,再求解一次,循环往复。但是这样是错误的,因为每次删除之后会对后面的选择产生影响。举个例子:
100 9 1
0 0 0
100 8 0
假设只能取两次,这样取会先取第一列,清零之后然后取第一行,和为 \(100+100+9+1=210\),但是如果直接取第一行和第三行,和为 \(100+9+1+100+8=218\),反而更优。
因此,先固定选择行的方案,然后对于列,取部分列的和最大的。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
typedef long long LL;
int n, m, k;
int a[N][N], col[N];
int cnt1(int x) {
int cnt = 0;
while (x) {
cnt += (x & 1);
x >>= 1;
}
return cnt;
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
int ans = 0;
for (int op = 0; op < (1 << n); op++) { // 固定行的选择
// 初步统计每列的和
memset(col, 0, sizeof col);
for (int j = 1; j <= m; j++)
for (int i = 1; i <= n; i++)
col[j] += a[i][j];
if (cnt1(op) > k)
continue;
int r = op, idx = 1, res = 0;
while (r) {
if (r & 1) { // 取该行
for (int j = 1; j <= m; j++) {
res += a[idx][j];
col[j] -= a[idx][j];
}
}
r >>= 1;
idx++;
}
sort(col + 1, col + m + 1);
for (int i = 0; i < min(k - cnt1(op), m); i++)
res += col[m - i];
ans = max(ans, res);
}
printf("%lld\n", ans);
return 0;
}
1028 贪心 · 例7-活动安排
解题思路
反证法:假设有一个最优解,但没有按照截止时间排序。如果交换两个活动的位置,看看是否会导致扣分减少。
假设有两个活动 \(A = (c_A, d_A)\) 和 \(B = (c_B, d_B)\),且 \(A\) 的截止时间比 \(B\) 早,即 \(d_A < d_B\)。但是在最优解中,先做了 \(B\),然后做了 \(A\),从而导致 \(A\) 的完成时间超过了 \(d_A\),产生了扣分。我们来分析交换这两个活动顺序的效果。
交换顺序前:
完成活动 \(B\) 后的时间是 \(t_B = c_B\)。
然后进行活动 \(A\),其完成时间是 \(t_A = t_B + c_A = c_B + c_A\)。
如果 \(t_A > d_A\),则扣分为 \(t_A - d_A\)。
交换顺序后:
如果先做活动 \(A\),完成时间为 \(t_A = c_A\)。
然后进行活动 \(B\),其完成时间是 \(t_B = t_A + c_B = c_A + c_B\)。
如果 \(t_B > d_B\),则扣分为 \(t_B - d_B\)。
通过交换活动顺序,\(A\) 的完成时间没有增加,而 \(B\) 的完成时间可能会变大。由于 \(d_A < d_B\),活动 \(A\) 的扣分更敏感。因此,在最优解中,如果交换顺序,\(A\) 的扣分不会增加,而 \(B\) 的扣分有可能增加。因此,如果不按照截止时间排序,活动的顺序可能导致更大的扣分,且无法得到更小的最大扣分。
结论:按照活动的截止时间 \(d_i\) 排序,优先完成截止时间较早的活动,是最优的策略。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long LL;
int n;
struct Node {
int c, d;
} a[N];
bool cmp(Node a, Node b) {
return a.d < b.d;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d%d", &a[i].c, &a[i].d);
sort(a + 1, a + n + 1, cmp);
LL current = 0, res = 0;
for (int i = 1; i <= n; i++) {
current += a[i].c;
res = max(res, current - a[i].d);
}
printf("%lld\n", res);
return 0;
}
1030 贪心 · 例9-保护那些花

解题思路
设牛的集合为 \((C={1,2,\dots,n})\)。每头牛 \((i\in C)\) 有两个参数:距离 \((t_i>0)\)(分钟)和吃花速度 \((d_i\ge0)\)(朵/分钟)。
对一个排列(运输顺序) \(\pi=(\pi(1),\pi(2),\dots,\pi(n))\)(这是 \((C)\) 的一个线性序列),定义第 \(k\) 个被运走的牛为 \(\pi(k)\)。当执行到第 \(k\) 头牛开始被运走时,已经过去的时间为
(每次往返花 \(2t\),被运走的牛在被运走期间不再吃花)。
因此被安排在位置 \(k\) 的牛 \(\pi(k)\) 在开始运输前已经吃掉的花数为
总体被吃掉的花为
为简洁起见,把常数 \(2\) 暂时忽略(不会影响最优顺序)。所以要最小化
定义二元关系 \(\prec\) 在 \(C\) 上:对于两头不同的牛 \((i,j)\),
等号时可按任意稳定规则断开平局,例如按编号。注意 \(i\prec j\) 等价于 \(\dfrac{t_i}{d_i} < \dfrac{t_j}{d_j}\)(当 \(d_i,d_j>0\) 时)。
证明最优序列 \(\pi\) 必须没有违反关系 \(\prec\) 的逆序(即若 \(i\prec j\) 则 \(i\) 不会排在 \(j\) 之后)。
只看某个相邻对(位置 \(p\) 与 \(p+1\)),设在某序列中相邻两头牛为 \(a\)(在前)和 \(b\)(在后)。只考虑这对对总花损的影响,其它牛不变。对这对:
- 若顺序为 \((\dots,a,b,\dots)\),这对产生的贡献是 \(t_a d_b\)(因为只在 \(u<k\) 且恰为这一对时产生交叉项)。
- 若顺序改为 \((\dots,b,a,\dots)\),这对产生的贡献变为 \(t_b d_a\)。
两种顺序的差值为
如果 \(\Delta>0\),则把 \((a,b)\) 互换能减少总花损(因为 \(t_b d_a < t_a d_b\)),如果 \(\Delta<0\) 则不必交换。如果 \(\Delta=0\) 则两种顺序等价。
因此,任一最优序列中不能存在使 \(\Delta>0\) 的相邻逆序对,也就是在最优序列中必有对于任意相邻的 \(a\)(前)和 \(b\)(后)满足
即 \(a\preceq b\)。
由相邻交换可消去任何逆序,对所有对都满足 \(t_i d_j \le t_j d_i\)(即按关系 \(\prec\) 非递减排列)的序列是局部无逆序的,从而是全局最优的。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n;
struct Node {
int t, d;
} a[N];
bool cmp(Node a, Node b) {
return a.t * b.d < b.t * a.d;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d%d", &a[i].t, &a[i].d);
sort(a + 1, a + n + 1, cmp);
long long res = 0, t = 0;
for (int i = 1; i + 1 <= n; i++) {
t += a[i].t;
res += (long long) 2 * t * a[i + 1].d;
}
printf("%lld\n", res);
return 0;
}
本文来自博客园,作者:Cocoicobird,转载请注明原文链接:https://www.cnblogs.com/Cocoicobird/p/19190534
浙公网安备 33010602011771号