2019 ICPC Southeastern European Regional
2019-2020 ICPC Southeastern European Regional Programming Contest (SEERC 2019)
B. Level Up
题意: s1和s2为level1和level2所需经验值;现在有n个任务,每个任务只能做一次,第i个任务在level1时需要 ti的时间,可以涨 xi经验值,在level2时使用时需要 ri的时间,可以涨yi的经验值。只有升完level1才能升level2,在level1溢出的经验值算到level2上。问升完2级所需要的最小时间。
题解:
设\(dp[i][j][k]\)为完成前i个任务,在level1时获得经验为j在level2时获得经验为k时所需的最小时间,然后更新即可
注意需要先将任务按照x从小到大排序,因为这样才能获得更多的溢出经验
在实现的时候第一维可以滚动掉
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 5e2 + 5;
typedef long long LL;
#define int LL
int n, s1, s2;
struct node {
int x, y, r, t;
} a[N];
int dp[N][N];
bool cmp(node a, node b) { return a.x < b.x; }
int sum = 0;
signed main() {
cin >> n >> s1 >> s2;
for (int i = 1; i <= n; i++) {
cin >> a[i].x >> a[i].t >> a[i].y >> a[i].r;
sum += a[i].x;
}
sort(a + 1, a + 1 + n, cmp);
memset(dp, 0x3f, sizeof dp);
dp[0][0] = 0;
int res = 0x3f3f3f3f;
for (int i = 1; i <= n; i++) {
for (int j = s1; j >= 0; j--) {
for (int k = s2; k >= 0; k--) {
if (dp[j][k] != 0x3f3f3f3f) {
int nj, nk;
if (j < s1) {
nj = j + a[i].x, nk = k;
if (nj > s1) {
nk = min(s2, k + nj - s1);
nj = s1;
}
dp[nj][nk] = min(dp[nj][nk], dp[j][k] + a[i].t);
}
nk = min(s2, k + a[i].y);
dp[j][nk] = min(dp[j][nk], dp[j][k] + a[i].r);
}
}
}
}
if (dp[s1][s2] == 0x3f3f3f3f3f3f3f3f) {
cout << -1 << endl;
} else {
cout << dp[s1][s2] << endl;
}
return 0;
}
D. Cycle String?
题意: 给你一个字符串,问你能否将字符串的位置改变使得字符串中不存在2个长度为n的相同的子串。\(1<=2*n<=10^6\)
题解: 思维+构造。因为只会存在一种字符长度大于等于n,因此我们只需要考虑处理这种字符。记这种字符为a,那么构造出来的序列为aaabaaabaaacaaada,这样每出现n次a,就插入一个其他的字符隔开。但是要考虑一种特殊情况,比如aaabaaab,这样就会导致aaba出现重复,这种情况为a的出现次数为2 * n - 2,其他字符出现2次,特判即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const MAXN = 2e6 + 10;
int num[MAXN];
char ans[MAXN];
int main() {
string s;
cin >> s;
int pos = 0;
for (int i = 0; i < s.size(); i++) {
num[s[i] - 'a']++;
}
int have = 0;
for (int i = 0; i < 26; i++) {
if (num[i]) have++;
if (num[i] > num[pos]) pos = i;
}
if(have==2&&num[pos]==(s.size()-2)&&num[pos]>(s.size()/2)){
cout << "NO" << endl;
return 0;
}
int m = s.size();
int n = s.size();
int cnt = 0;
int now = 0;
while (m--) {
ans[cnt++] = char(pos + 'a');
now++;
if (now == num[pos]) {
now = 0, num[pos] = 0;
break;
}
if (now >= (n + 1) / 2) {
int f = 0;
for (int i = 0; i < 26; i++) {
if (i == pos) continue;
if (num[i]) {
ans[cnt++] = char(i + 'a');
num[i]--;
f = 1;
break;
}
}
if (f) {
num[pos] -= now;
now = 0;
}
}
}
for (int i = 0; i < 26; i++) {
if (!num[i]) continue;
while (num[i]) ans[cnt++] = i+'a',num[i]--;
}
if (ans[0] == ans[n - 1]) {
cout << "NO" << endl;
} else {
cout << "YES" << endl;
for (int i = 0; i < n; i++) {
cout << ans[i];
}
}
return 0;
}
F. Game on a Tree
题意: 给定一棵根结点为 的树,一开始全白,alice和bob轮流玩,Alice选择一个点,将其染黑,接下来Bob将这个点的某个祖先点或其子树点染黑,2个人轮流来,最后不能走的人输,问最终谁获胜
题解:
求树上完美匹配,如果存在完美匹配那么bob一定赢,因为他只需要每次选择alice的匹配的那个点即可,否则alice赢
dp代表以这个点为根的子树中(包括这个点)有多少个没被匹配的点,只需要判断子节点是否有没有匹配的点,如果有的话当前点可以被匹配,否则当前点不能被匹配
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int n;
vector<int> mp[N];
int dp[N];
void dfs(int now, int fa) {
for (int i = 0; i < mp[now].size(); i++) {
int ne = mp[now][i];
if (ne == fa) continue;
dfs(ne, now);
dp[now] += dp[ne];
}
if (dp[now] == 0) dp[now] = 1;
else dp[now]--;
}
int main() {
cin >> n;
for (int i = 0; i < n - 1; i++) {
int x, y;
cin >> x >> y;
mp[x].push_back(y), mp[y].push_back(x);
}
dfs(1, 0);
if (dp[1]) cout << "Alice" << endl;
else cout << "Bob" << endl;
return 0;
}
G. Projection
题意: 现在有一个三维图像的正视图和侧视图,问符合正视图和侧视图的三维图像的最大方块数目和最小数目为多少,打印最大和最小数目,并打印每个方块的三维坐标(要求字典序最小)?三维图像的三维范围 \(1<=n,m,k<=100\)
比如下面样例就是tersorflow的正视图和侧视图
111
010
010
010
010
111
100
110
100
100
题解: 构造。
对于最大的情况,那么就是我能放就放,因此我可以遍历所有的格子,然后判断这个格子是否满足正视图和侧视图,如果都满足,那么就取这个格子,这样的情况是最大的。
对于最小的情况,因为要字典序最小,因此先枚举侧面的点,然后往后看正面是否可以放。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const MAXN = 1e2 + 10;
int X, Y, Z;
char mp1[MAXN][MAXN], mp2[MAXN][MAXN];
int cnt1[MAXN], cnt2[MAXN];
char num1[MAXN][MAXN], num2[MAXN][MAXN];
int mp[MAXN][MAXN][MAXN];
int main() {
scanf("%d %d %d", &X, &Y, &Z);
for (int x = 0; x < X; x++) scanf("%s", mp1[x]);
for (int x = 0; x < X; x++) scanf("%s", mp2[x]);
int ans = 0;
for (int x = 0; x < X; x++) {
for (int y = 0; y < Y; y++) {
for (int z = 0; z < Z; z++) {
if (mp1[x][y] == '1' && mp2[x][z] == '1') {
mp[x][y][z] = 1, ans++;
num1[x][y] = 1, num2[x][z] = 1;
}
}
}
for (int y = 0; y < Y; y++)
if (mp1[x][y] != char(num1[x][y] + '0')) return puts("-1"), 0;
for (int z = 0; z < Z; z++)
if (mp2[x][z] != char(num2[x][z] + '0')) return puts("-1"), 0;
}
printf("%d\n", ans);
for (int x = 0; x < X; x++) {
for (int y = 0; y < Y; y++) {
for (int z = 0; z < Z; z++) {
if (mp[x][y][z]) printf("%d %d %d\n", x,y,z);
mp[x][y][z] = 0;
}
}
}
ans = 0;
for (int x = 0; x < X; x++) {
int pos_y = 0, pos_z = 0;
while (mp1[x][pos_y] == '0' && pos_y < Y) pos_y++;
while (mp2[x][pos_z] == '0' && pos_z < Z) pos_z++;
int c1 = 0, c2 = 0;
int y = 0, z = 0;
while (y < Y) {
if (mp1[x][y] == '1') c1++;
y++;
}
while (z < Z) {
if (mp2[x][z] == '1') c2++;
z++;
}
y = 0, z = 0;
int k = abs(c1 - c2);
if (c1 > c2)
while (y < Y && k) {
if (mp1[x][y] == '1')
mp[x][y][pos_z] = 1, ans++, mp1[x][y] = '0', k--;
y++;
}
if (c1 < c2)
while (z < Z && k) {
if (mp2[x][z] == '1')
mp[x][pos_y][z] = 1, ans++, mp2[x][z] = '0', k--;
z++;
}
y = 0, z = 0;
while (y < Y && z < Z) {
while (mp1[x][y] == '0' && y < Y) y++;
while (mp2[x][z] == '0' && z < Z) z++;
if (y >= Y || z >= Z) break;
mp[x][y][z] = 1, ans++;
y++, z++;
}
}
printf("%d\n", ans);
for (int x = 0; x < X; x++) {
for (int y = 0; y < Y; y++) {
for (int z = 0; z < Z; z++) {
if (mp[x][y][z]) printf("%d %d %d\n", x,y,z);
}
}
}
return 0;
}
I. Absolute Game
题意: 2个人玩博弈游戏,a和b每次可以拿一个数字,a希望a数组剩下的是最和b数组剩下的数字的差值最大,b希望这个差值最小,a先手,当双方都按照最优方案进行博弈时,最后答案值为多少?
题解: 思维、签到。对于a每个剩下的数字,只会是大于等于它的最小数字,和小于它的最大数字,因此每个数字都进行差值,取个min,然后每个数字的答案取个max即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int n;
int a[N], b[N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n; i++) cin >> b[i];
sort(a, a + n), sort(b, b + n);
int res = -0x3f3f3f3f;
for (int i = 0; i < n; i++) {
int pos = lower_bound(b, b + n, a[i]) - b;
int tmp;
if (pos == 0) {
tmp = abs(b[pos] - a[i]);
} else if (pos == n) {
tmp = abs(b[pos - 1] - a[i]);
} else {
tmp = min(abs(b[pos] - a[i]), abs(b[pos - 1] - a[i]));
}
res = max(res, tmp);
}
cout << res << endl;
return 0;
}
J. Graph and Cycles
题意: 给你一张n个点的完全图,现在要把这张完全图划分为若干个互不相交的环,环上每条边ei对于答案的贡献为max(ei, ei+1)。求所有环对答案的最小贡献
题解: 思维。因为每条边对于答案的贡献为相邻边取max,我们可以把边的贡献放到点上,那么就是找到每个点对于答案的贡献。而每个点连着n条边,那么就会在n / 2 个环内,那么为了让答案最小,那么就是排序完两两一组,比如123456,那么贡献就是2, 4, 6。
代码:
#include <bits/stdc++.h>
int const MAXN = 2e6 + 10;
typedef long long ll;
using namespace std;
ll ans = 0;
bool vis[MAXN * 2];
int tot, n, st;
struct Edge1 {
int u, v;
ll w;
} e[MAXN * 2];
struct Edge {
int to, next;
ll w;
} edge[MAXN * 2];
int head[MAXN];
void init() {
tot = 0;
memset(head, -1, sizeof(head));
memset(vis, false, sizeof(vis));
}
void addedge(int u, int v, int w) {
edge[tot].to = v;
edge[tot].w = w;
edge[tot].next = head[u];
head[u] = tot++;
}
bool cmp(Edge1 e1, Edge1 e2) { return e1.w < e2.w; }
int main() {
init();
scanf("%d", &n);
for (int i = 1; i <= n * (n - 1) / 2; i++) {
scanf("%d%d%lld", &e[i].u, &e[i].v, &e[i].w);
}
sort(e + 1, e + 1 + n * (n - 1) / 2, cmp);
for (int i = 1; i <= n * (n - 1) / 2; i++) {
addedge(e[i].u, e[i].v, e[i].w);
addedge(e[i].v, e[i].u, e[i].w);
}
for (int i = 1; i <= n; i++) {
for (int j = head[i]; ~j; j = edge[j].next) {
ans += edge[j].w;
j = edge[j].next;
}
}
cout << ans << endl;
return 0;
}