比赛链接:牛客IOI周赛19-普及组
A - 小y的考试
取四个选项的长度进行排序,若前两个数大小不同则最小值唯一,若后两个数大小不同则最大值唯一,否则输出“C”。
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int t;
struct choice {
int len;
char id;
bool operator < (const choice &x) const {
return len < x.len;
}
} cho[4];
string a, b, c, d;
int main() {
cin >> t;
while (t--) {
cin >> a >> b >> c >> d;
cho[0].len = a.length(), cho[0].id = 'A';
cho[1].len = b.length(), cho[1].id = 'B';
cho[2].len = c.length(), cho[2].id = 'C';
cho[3].len = d.length(), cho[3].id = 'D';
sort(cho, cho + 4);
if (cho[0].len < cho[1].len) {
cout << cho[0].id << endl;
} else if (cho[3].len > cho[2].len) {
cout << cho[3].id << endl;
} else {
cout << 'C' << endl;
}
}
return 0;
}
B - 小y的序列
若寻找错误的数字,则还需要确定哪些数字是正确的,所以反向寻找正确的数字。
由于已经知道数列的规律,所以我们可以规定一串数列,则其他所有满足条件的数列都可以由该数列所有数同时加上或减去一个数得到。因此,我们只要拿含有错误数字的目标数列和这个基准数列各位作差,出现次数最多的差值,即为基准数列与正确的目标数列的差值,记录此差值出现的次数,即为正确的数字个数,n减去正确数字的个数,就得到最少的修改次数。
#include <iostream>
#include <map>
using namespace std;
#define ll long long
#define io_speed_up ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define MAXN 100000 + 10
int n, ans, x;
map <ll, int> mat;
ll temp[MAXN];
int main() {
io_speed_up;
cin >> n;
for (int i = 1; i <= n; ++i) {
temp[i] = temp[i - 1] + i - 1;
}
for (int i = 1; i <= n; ++i) {
cin >> x;
ans = max(ans, ++mat[x - temp[i]]);
}
cout << n - ans << endl;
return 0;
}
C - 小y的旅行
不难想到用并查集来写。
首先把两个点都大于k的边给合并,这个并不影响结果;然后判断边里有小于k的点的合并,如果之前已经在一个连通块内,则不需要加入这条边,即删除这条边。
#include <iostream>
using namespace std;
#define io_speed_up ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define MAXN 1000000 + 10
#define MAXM 2000000 + 10
int fa[MAXN];
struct Edge {
int x, y;
} edge[MAXM];
int n, m, k, ans;
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int main() {
io_speed_up;
cin >> n >> m >> k;
for (int i = 1; i <= n; ++i) fa[i] = i;
for (int i = 1; i <= m; ++i) {
cin >> edge[i].x >> edge[i].y;
if (edge[i].x > k && edge[i].y > k) {
int fax = find(edge[i].x), fay = find(edge[i].y);
if (fax != fay) {
fa[fax] = fay;
}
}
}
for (int i = 1; i <= m; ++i) {
int x = edge[i].x, y = edge[i].y;
if (x <= k || y <= k) {
int fax = find(x), fay = find(y);
if (fax == fay) {
ans++;
} else {
fa[fax] = fay;
}
}
}
cout << ans << endl;
return 0;
}
D - 小y的游戏
首先考虑搜索剪枝,发现超时;
再考虑四维dp,dp[i][j][k][l]表示到第i只怪物时,使用j次伤害为9的冲击、k次伤害为3的冲击、l次伤害为1的冲击的情况的可行性,依旧超时;
思考发现,四维dp的最后一位可以用技巧优化掉,把dp数组换成int类型,记录一下前两个冲击的使用次数所导致伤害为1的冲击最少需要用几次,然后我们对这个最小次数二分答案。
#include <iostream>
using namespace std;
#define io_speed_up ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define INF 0x3f3f3f3f
int dp[25][210][210], blood[25], maxn, n;
bool check(int x) {
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= x; ++j) {
for (int k = 0; k <= x; ++k) {
dp[i][j][k] = INF;
}
}
}
dp[0][0][0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j * 9 <= blood[i] + 8; ++j) {
// +8是因为血量不足9时也可以用伤害为9的冲击
for (int k = 0; k * 3 + j * 9 <= blood[i] + 8; ++k) {
if (j + k + max(0, blood[i] - j * 9 - k * 3) > x) {
continue;
}
for (int p = j; p <= maxn; ++p) {
for (int q = k; q <= maxn; ++q) {
dp[i][p][q] = min(dp[i][p][q], dp[i - 1][p - j][q - k] + max(0, blood[i] - j * 9 - k * 3));
}
}
}
}
}
for (int i = 0; i <= x; i++) {
for (int j = 0; j <= x; j++) {
if (dp[n][i][j] <= x) {
return true;
}
}
}
return false;
}
int main() {
io_speed_up;
int t;
cin >> t;
while (t--) {
cin >> n;
maxn = min(n * 7, 93);
/*
假设所有怪物血量全为60且每次只用伤害为9的冲击,则每只怪物要使用7次冲击
由样例可知当n = 20且所有怪物血量全为60时要使用93次冲击
*/
for (int i = 1; i <= n; ++i) {
cin >> blood[i];
if (blood[i] < 0) {
blood[i] = 0;
}
}
int l = 0, r = maxn;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) {
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << l << endl;
}
return 0;
}