2023.6.15 每日一题
原题链接:
A - Codeforces Round 666 (Div. 1) - C
B - Educational Codeforces Round 82 (Rated for Div. 2) - C
A. Monster Invaders - 2300
题目大意
在一款RPG当中,有两种类型的怪物,普通怪物血量为 \(1\),boss的血量为 \(2\)。
我们有三种攻击手段:
-
手枪,对一个怪物造成1点伤害,装填时间为r1。
-
激光枪,对当前关卡中的所有怪物(包括boss)造成1点伤害,装填时间为r2。
-
AWP,即刻击杀任何怪物,装填时间为r3。
枪支初始时未装填,一次只能装填一把枪。
如果对boss造成伤害但是没能打死boss,我们就必须移动到隔壁的关卡,其他时刻可以自选是否移动到隔壁的关卡,每次移动时间为 \(d\),移动过程中不可以攻击或换弹。
问通关游戏的最短时间为多少。
解题思路
传统dp
我们首先确定基准时间啊,也就是从第一个关卡到最后一个关卡一定需要 \((n - 1)d\) 的传送时间。
我们dp数组,直接存放通前 \(i\) 关的最短用时。
如果我们想要不换关卡clear一关的话,只能通过先拿手枪干掉所有小怪,再一发AWP击杀boss,这里的用时先存到clear数组中。
我们考虑转移,最基础的情况就是从上一关过来不换关卡花掉 clear 的时间,另外的转移方式是打通了上上关,在打上一关时使用激光枪或者打到剩下boss再开一枪手枪,这样需要传送到这一关,然后这一关再次抉择使用激光枪或者打到剩下boss再开一枪手枪回到上一关。这样这两关需要的时间就是dp[i - 2] + min({c[i], r1 * (a[i] + 2), r1 + r2}) + min({c[i - 1], r1 * (a[i - 1] + 2), r1 + r2}) + 2 * d
,除此之外,还有一种转移的方式,就是三个关卡一起考虑,方法同上。因为一个关卡只能跳到相邻两关所以最多只需要考虑到三个关卡同时打。
最后还需要处理一下后缀和的特殊情况,就是从某一关开始不去其他关卡了,全结果取min即可。
前后缀
除了如上dp之外,本题还可以通过前后缀dp来考虑。
我们分段dp从 \(1\) 到 \(i\) 和从 \(i\) 到 \(n\) 的最小时间,每次转移的考虑和上面类似不再赘述。最后的结果就是每种中间切换前后缀的间断点的情况取最小值。
AC Code
dp Code
#include <iostream>
#include <algorithm>
#include <cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const int MOD = 1e9 + 7;
LL a[N], c[N];
LL dp[N], s[N];
void solve() {
LL n, r1, r2, r3, d;
cin >> n >> r1 >> r2 >> r3 >> d;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) {
c[i] = a[i] * r1 + r3;
}
for (int i = 1; i <= n; ++i) {
dp[i] = dp[i - 1] + c[i];
dp[i] = min({dp[i], dp[i - 2] + min({c[i], r1 * (a[i] + 2), r1 + r2}) + min({c[i - 1], r1 * (a[i - 1] + 2), r1 + r2}) + 2 * d, dp[i - 3] + min({c[i], r1 * (a[i] + 2), r1 + r2}) + min({c[i - 1], r1 * (a[i - 1] + 2), r1 + r2}) + min({c[i - 2], r1 * (a[i - 2] + 2), r1 + r2}) + d * 4});
}
LL res = dp[n];
s[n] = c[n];
for (int i = n - 1; i >= 0; --i) {
s[i] = min({c[i], r1 * (a[i] + 2), r1 + r2}) + s[i + 1];
}
for (int i = n; i >= 0; --i) {
res = min(res, dp[i - 1] + s[i] + (n - i) * d);
}
cout << res + (n - 1) * d << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
prefix and suffix code
#include <iostream>
#include <algorithm>
#include <cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const int MOD = 1e9 + 7;
LL a[N];
LL pre[N], suf[N];
void solve() {
LL n, r1, r2, r3, d;
cin >> n >> r1 >> r2 >> r3 >> d;
for (LL i = 1; i <= n; i ++) {
cin >> a[i];
}
for (LL i = 1; i <= n; i ++) {
pre[i] = pre[i - 1] + min(d + r3 + r1 * a[i], 3 * d + min(r1 + r2, r1 * (a[i] + 2)));
if (i >= 2) {
pre[i] = min(pre[i], pre[i - 2] + 4 * d + min(r1 + r2, r1 * (a[i] + 2)) + min(r1 + r2, r1 * (a[i - 1] + 2)));
}
}
for (LL i = n; i >= 1; i --) {
if (i == n) {
suf[i] = min(r3 + r1 * a[i], 2 * d + min(r1 + r2, r1 * (a[i] + 2)));
} else {
suf[i] = suf[i + 1] + 2 * d + min(r1 + r2, r1 * (a[i] + 2));
}
}
LL res = min(suf[1], pre[n] - d);
for (int i = 1; i < n; i --) {
res = min(pre[i] + suf[i + 1], res);
}
cout << res << endl;
}
signed main() {
ios;
int T = 1;
// cin >> T;
while (T--) {
solve();
}
}
B. Perfect Keyboard - 1600
题目大意
给定poly的密码,他希望密码每个相邻字符在线性键盘上也相邻。那么问能否有这样的键盘使得满足poly的要求,如果有,给出键盘布局。
解题思路
直接模拟这个过程即可,构造一个结果串,同时记录多少字符是有限制的,逐个输出即可。
AC Code
#include <iostream>
#include <algorithm>
#include <cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;
const int N = 4e5 + 10;
const int MOD = 1e9 + 7;
void solve() {
vector<int> vis(28);
string s, t = "#";
cin >> s;
vis[s[0] - 'a'] = true;
t += s[0];
t += "#";
int pos = 1;
for (int i = 1; i < s.length(); ++i) {
if (s[i] == s[i - 1]) {
cout << "NO" << endl;
return;
}
if (t[pos + 1] == s[i]) {
pos++;
} else if (t[pos - 1] == s[i]) {
pos--;
} else if (vis[s[i] - 'a']) {
cout << "NO" << endl;
return;
} else if (t[pos + 1] == '#') {
t[pos++ + 1] = s[i];
t += '#';
vis[s[i] - 'a'] = true;
} else if (t[pos - 1] == '#') {
t[pos - 1] = s[i];
t = "#" + t;
vis[s[i] - 'a'] = true;
} else
cout << "NO" << endl;
return;
}
}
cout << "YES" << endl;
t = t.substr(1, t.length() - 2);
for (int i = 0; i < 26; ++i) {
if (!vis[i]) {
t += char(i + 'a');
}
}
cout << t << endl;
}
signed main() {
ios;
int T = 1;
cin >> T;
while (T--) {
solve();
}
}