特性声明
本次继续尝试把部分题解放进代码的形式。
题意复述在代码里面,外面只会补充一些细节。
A - GamingForces
#include <bits/stdc++.h>
#define long long long
#define fori(n) for (long i = 0; i < n; i++)
#define forj(n) for (long j = 0; j < n; j++)
#define rofi(n) for (long i = n - 1; i >= 0; i--)
#define rofj(n) for (long j = n - 1; j >= 0; j--)
using namespace std;
/* REQUIRED */ const long N = 104;
// 有一堆血量不同的怪物
// 可以随意使用技能
// 1. 打两个怪一滴血
// 2. 直接杀掉一个怪
// 求最少使用技能的次数
// 直接贪心即可
// 存储数据
int t, n, h[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> t;
while (t--) {
cin >> n;
fori(n) cin >> h[i];
// 排序便于贪心
sort(h, h + n);
// 统计次数
int f = 0;
// 从大到小
rofi(n) {
// 血量大于一直接杀
if (h[i] > 1)
f++;
else {
// 否则剩下的全是一
// 一起处理然后弹出
f += (i + 2) / 2;
break;
}
}
cout << f << endl;
}
return 0;
}
要特别注意对血量为一的怪物的处理:
升序排序后从大到小扫,扫到第一个血量为一的怪物时编号为 i
,那么 i + 1
是血量为一的怪物的总数,需要攻击的次数是向上取整的 (i + 1) / 2
,也就是向下取整的 ((i - 1) + 1) / 2 + 1 = i / 2 + 1
。
B - Stand-up Comedian
#include <bits/stdc++.h>
#define long long long
#define fori(n) for (long i = 0; i < n; i++)
#define forj(n) for (long j = 0; j < n; j++)
#define rofi(n) for (long i = n - 1; i >= 0; i--)
#define rofj(n) for (long j = n - 1; j >= 0; j--)
using namespace std;
/* REQUIRED */ const long N = 0;
// 对两个观众满意不满意分为四种笑话
// 每个人有情绪值
// 满意则加一,不满意则减一
// 负数之后离场,直接一切停止
// 要尽可能讲更久的笑话
// 先存好数据
int t, a[4];
// 首先让两个人都满意的笑话肯定先讲
// 那么有两种情况
// 1. 没有这样的笑话 // 总共只能讲一个,因为保证了至少一个笑话
// 2. 有这样的笑话 // 中间两种情况可以一比一抵消
// 抵消之后每讲一个笑话一定有个人恒定减一
// 判断笑话先讲完还是先有个人遗憾立场即可
// 验证样例:
// [0] 2 5 10 6 // 初始
// [2] 0 5 10 6 // 贪心
// [12] 0 0 5 6 // 抵消,每个人满意度都是 2
// [15] 0 0 5 3 // 剩下所有的都一定让某人不满意
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> t;
while (t--) {
fori(4) cin >> a[i];
if (a[0] == 0) {
cout << 1 << endl;
} else {
cout << a[0] + min(a[1], a[2]) * 2 +
min(abs(a[1] - a[2]) + a[3], a[0] + 1)
<< endl;
}
}
return 0;
}
C - Min Max Sort
#include <bits/stdc++.h>
#define long long long
#define fori(n) for (long i = 0; i < n; i++)
#define forj(n) for (long j = 0; j < n; j++)
#define rofi(n) for (long i = n - 1; i >= 0; i--)
#define rofj(n) for (long j = n - 1; j >= 0; j--)
using namespace std;
/* REQUIRD */ const long N = 2e5 + 4;
// 给定排列
int p[N], n;
// 一个操作有以下步骤
// 1. 移除两个数字
// 2. 从头插入两个数字最小值
// 3. 从尾插入两个数字最大值
// 找到最小的升序排序次数
// 感觉会有一些比较恶心的情况
// 比如 1 2 3 5 4 6 7 8
// 目前只能想到最坏的 4 次的解法
// 基本可以证明 4 次也是最优
// 样例中
// 两个 worst case
// 一个 best case
// 一个 average case
// 找到最大的 sorted 内核即可
// 例如 8 3 7 4 5 6 2 1,sorted 内核为 3 4 5 6
// 但是本题不可 n^2 暴力
// 但可以把每个数出现的位置存下来
// 比如 a[8] = 0,表示 8 在第一个位置
int a[N];
// 那么每次出现逆序的时候记录下来
// 偶数时,例如对于 n = 8
// a[5] < a[4] 则无 pairs = 1 内核
// a[4] a[6] 逆序则无 pairs = 2 内核……
// 结果为 f = i - 1 或 n + 1 - i
int f;
// 奇数时,例如对于 n = 9
// 结果也是一样的
// 那么记录题目数据,开始吧!
int t;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> t;
while (t--) {
cin >> n;
fori(n) {
cin >> p[i];
a[p[i]] = i;
}
f = 0;
fori(n) {
if (i == 0)
continue;
// 这里 i + 1 才是指向逆序对后者的指针
if (a[i + 1] < a[i]) {
int m = min(i, n - i);
f = max(f, m);
}
}
cout << f << endl;
}
return 0;
}
D - Fixed Prefix Permutations
前面往置换环的方向想,浪费了不少时间,其实核心就在
- 将排列视为变换
- 使用字典树存储逆排列便于查询前缀
#include <bits/stdc++.h>
#define long long long
#define fori(n) for (long i = 0; i < n; i++)
#define forj(n) for (long j = 0; j < n; j++)
#define rofi(n) for (long i = n - 1; i >= 0; i--)
#define rofj(n) for (long j = n - 1; j >= 0; j--)
using namespace std;
/* REQUIRED */ const long N = 5e4 + 4;
// 给定 n 个长度为 m 的排列
// 这里 a[i][j] 表示第 i 个排列第 j + 1 个数字
// 从 0 开始
int n, m, a[N][16];
// 美丽度 k 是最大的 1, 2, 3... 排列前缀长度
// 可以为 0
// 排列乘积 p x q = 先 p 后 q 复合
// 对于每个排列,找到选任意排列放后面相乘后最大的美丽值
// 挺难的……
// 先存个 t
int t;
// 看看样例吧!
// 还挺充实的
// 但是没有解释,那只能自己解释了
// 第一个样例
// [2 4 1 3] x [2 1 3 4] = [1 4 2 3], k = 1
// [1 2 4 3] x [1 2 4 3] = [1 2 3 4], k = 4
// [2 1 3 4] x [2 1 3 4] = [1 2 3 4], k = 4
// 对于每个排列,要在低于 n 的时间搞定
// 可能要依据排列的性质进行排序
// 字典序排序目前感觉没有大用
// 比较麻烦
// 尝试把排列视为置换
// 那么 3 以上的置换圈不能自己回到原位
// 第一个样例
// [1243] [1 2 34] [12 3 4]
// 21 是 1243 最大重合逆序,因此应用
// 目标从 1 开始似乎是一个好想法
// 如果修改成
// [1243] [3421]
// 那么这两个是最理想的配对
// 现在考虑多个置换圈的情况
// 如果修改成
// [1243 5 678] [34215 876]
// 那么最大匹配长度为 4
// 能否使用 Trie?
// 把每个排列处理成从下到上置换圈字符形式
// 0 代表分界
// 预处理 O(n),匹配 O(m)
namespace trie {
int nex[N * 10][16], cnt;
int ver[N * 10][16];
// 记录当前版本
int v;
void reset() {
v++;
cnt = 0;
}
void insert(int *s, int l) {
int p = 0;
for (int i = 0; i < l; i++) {
int c = s[i];
// 如果没有,就添加结点
if (ver[p][c] < v) {
nex[p][c] = ++cnt;
ver[p][c] = ***ex[p][c];
}
}
int find(int *s, int l) {
int p = 0;
int f = 0;
for (int i = 0; i < l; i++) {
int c = s[i];
if (ver[p][c] < v)
return f;
p = nex[p][c];
f++;
}
return f;
}
} // namespace trie
// 置换圈断裂的话显然不能这么想
// 应该看目标
// 处理出一个逆排列 b
// 依然从 0 开始,便于匹配
int b[16];
// 然后对逆排列匹配即可
// qwq 亏我想这么久
// Trie 才是关键
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> t;
while (t--) {
cin >> n >> m;
trie::reset();
fori(n) {
forj(m) {
cin >> a[i][j];
b[a[i][j] - 1] = j + 1;
}
trie::insert(b, m);
}
fori(n) {
// 用 b 做临时数组
forj(m) b[j] = a[i][j];
cout << trie::find(b, m) << ' ';
}
cout << endl;
}
return 0;
}