Codeforces Round #666 Div 2 A-E题解
A-E题题解
A. Juggling Letters
题意:
给定\(n\)个字符串,每次可以交换任意两个字符串的两个字符,问能否将这\(n\)个字符串都变得相同?
思路:
对所有\(n\)个字符串的每字母进行桶排序,如果存在某一种字母的个数\(\bmod n \neq 0\),那么答案就是$NO \(,否则就是\)YES$。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
const int maxn = 1005;
using namespace std;
int T, n, len;
char s[maxn];
int buck[26];
int main() {
scanf("%d", &T);
while (T--) {
memset(buck, 0, sizeof(buck));
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%s", s);
len = strlen(s);
for (int i = 0; i < len; i++)
buck[(int)s[i] - 'a']++;
}
int flag = 1;
for (int i = 0; i < 26; i++)
if (buck[i] % n) flag = 0;
if (flag)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
B. Power Sequence
题意:
如果对于数组\(a_0 \sim a_{n-1}\),存在一个正整数\(c\),满足\(a_i = c^i\),那么称数组\(a\)是一个power sequence。我们可以将数组随意排序。我们每次操作可以将某个\(a_i\)加一或减一,问我们要把数组\(a\)变成power sequence,至少要操作多少次?
思路:
根据题意,我们先把数组\(a\)从小到大排序。
指数的增长非常快,题目数据\(a_i \leqslant 10^9\),我们考虑\(y=2^x\)的图像,当\(x > 63\)时,显然就已经会爆掉long long了。而此时,对于最后一个数,它的操作数显然要超出long long的范围。那么此时,显然我们把她们都变成\(1\)要更优(都变成\(1\)是不会爆long long的)。所以,当\(n \geqslant 63\)时,我们令\(ans = \sum\limits_{i=1}^{n} (a_i - 1)\)。
当\(n < 63\)时,我们考虑最后一个数(也就是最大的数)。我们容易想到,我们要找一个\(x\),满足\(x^{n-1} = a_n\)。那么\(x = \sqrt[n-1]{a_n} = e^{\tfrac{1}{n-1}\ln{a_n}}\)。所以我们从\(1\)到\(\left\lfloor e^{\tfrac{1}{n-1}\ln{a_n}} \right\rfloor + 1\)枚举\(x\),然后计算操作次数即可。
代码:
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
typedef long long ll;
using namespace std;
const int maxn = 1e5 + 5;
int n;
ll a[maxn], ans;
ll fpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res *= a;
b >>= 1;
a *= a;
}
return res;
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%lld", a + i);
sort(a, a + n);
if (n >= 64) {
for (int i = 0; i < n; i++)
ans += (a[i] - 1);
printf("%lld\n", ans);
return 0;
}
ans = 0x5f5f5f5f5f5f5f5f;
ll tmp = 0;
ll upbound = exp(log(a[n - 1]) / (n - 1)) + 1;
for (int i = 1; i <= upbound; i++) {
tmp = 0;
for (int j = 0; j < n; j++) {
tmp += abs(a[j] - fpow(i, j));
}
ans = min(ans, tmp);
}
printf("%lld\n", ans);
return 0;
}
C. Multiples of Length
题意:
给定一个长度为\(n\)的数组\(a\)。你必须恰好进行如下操作三次:
- 选择数组的一段\([l, r]\),给每个数都加上(可以是负数或则\(0\))一个数。这个数必须是\(r - l + 1\)的倍数。
你必须通过这三次操作把数组的所有数都变成\(0\)。
思路:
考虑数论的知识。首先,我们可以选定一个数,然后把它变成\(0\)(直接加上\(-a_i\)就可以了)。然后,显然,\(n\)和\(n - 1\)只相差\(1\)。那么,第一次操作,我们先将\(a_i\)变成\(0\);第二次操作,我们把\(a_2 \sim a_n\)分别加上\((n - 1)\cdot a_i\),此时他们都变成了\(n\cdot a_i\);第三次操作,我们把\(a_1 \sim a_n\)都减去他们自身就可以了(此时它们都是\(n\)的倍数,\(a_1 = 0\),也是\(n\)的倍数)。
注意\(n = 1\)的情况。
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long ll;
using namespace std;
const int maxn = 1e5 + 5;
ll n;
ll a[maxn], b[maxn];
int main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
scanf("%lld", a + i);
if (n == 1) {
printf("1 1\n%lld\n1 1\n0\n1 1\n0\n", -a[1]);
return 0;
}
printf("1 1\n");
printf("%lld\n", -a[1]);
printf("2 %lld\n", n);
for (int i = 2; i <= n; i++) {
printf("%lld ", a[i] * (n - 1));
}
putchar('\n');
printf("1 %lld\n", n);
printf("0");
for (int i = 2; i <= n; i++) {
printf(" %lld", -a[i] * n);
}
putchar('\n');
return 0;
}
D. Stoned Game
题意:
T和HL在玩游戏,T先手。有\(n\)堆石头,每堆石头有\(a_i\)个。他们每次可以选择某一堆石头,取走其中的一个。玩家不能选择上一回合被选择的那一堆石头(一定是被对手选的;或者是第一局,没有人选,则所有的石头堆都可以选)。如果某一回合,玩家没有石头可以取了,那么它就输了。两人都一直取最优策略,问最后谁赢?
思路:
对于两个玩家来说,每次取能取的石头堆中石头最多的那一堆,一定是最优的,维护一个优先队列就可以了。证明本蒟蒻不会QwQ,请感性理解一下。
代码:
比赛的时候代码写的比较随意QwQ
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
struct P {
int val, id; // id用来存储这一堆石头上一次被选择是哪一回合
P(int val = 0, int id = 0): val(val), id(id) {}
bool operator < (const P &rhs) const {
return val < rhs.val;
}
};
int T, n;
ll ans[2];
int main() {
scanf("%d", &T);
while (T--) {
int cur = 2; // cur = 2是因为从0开始计数的话,id会出错;而且从2开始下面判断也方便。
ans[0] = ans[1] = 0;
scanf("%d", &n);
priority_queue<P> p;
for (int i = 0; i < n; i++) {
int x;
scanf("%d", &x);
if (x > 0)
p.push(P(x, 0));
}
while (!p.empty()) {
P u = p.top();
p.pop();
if (u.id <= cur - 2) { // 上面说的方便就是这里,不用特判id == 0
u.id = cur;
u.val--;
if (u.val)
p.push(u);
}
else {
if (p.empty()) break;
P u2 = p.top();
p.pop();
p.push(u);
u2.id = cur;
u2.val--;
if (u2.val)
p.push(u2);
}
cur++;
}
if (cur & 1)
printf("T\n");
else
printf("HL\n");
}
return 0;
}
E. Monster Invaders
题意:
有一个游戏,有\(n\)层,第\(i\)层有\(a_i\)个小怪,\(1\)个Boss。每个小怪\(1\)滴血,每个Boss\(2\)滴血。一开始你在第一层。你有三种枪:
- 手枪:打一个目标一滴血,装填时间为\(r_1\);
- 激光枪:对该层所有目标打一滴血(包括Boss),装填时间为\(r_2\);
- AWP:直接杀死一个怪(可以直接杀死Boss),装填时间为\(r_3\)。
如果你对Boss造成了伤害,但是没有打死,那么你必须转移到相邻的一层。转移的时间为\(d\)。你也可以随时跑到相邻的一层,花费时间为\(d\)。当你没有清掉某一层的小怪的时候,你不能打Boss(除了用激光枪)。问要把所有Boss清掉,你最少要花费多少时间?
思路:
这题麻烦就麻烦在,如果一枪干不死Boss,就必须要转移,所以我们考虑把状态都存下来。因为转移的时候,我们只能转移到相邻的层,所以,如果我们在第\(1\)层给Boss留了一滴血,我们打到第\(n\)层以后再返回来,此时\(2 \sim n - 1\)层我们就都经过了两次,这种情况显然比较差。此时就不如我们在第二层的时候,就倒回去把第一层清理掉。
我们考虑用\(dp[i][j]\)来存储到达第\(i\)层,Boss还剩\(j\)滴血时,我们花费的时间(开\(dp[n + 5][2]\)的数组就可以了)。那么,\(dp[1][0] = \operatorname{min}(r_1, r_3) \cdot a_1 + r_3, dp[1][1] = \operatorname{min}(r_2, (a[1] + 1) \cdot r_1)\)。
然后我们考虑转移,转移一共考虑四种情况:
- 上一层全清,当前层也全清;
- 上一层全清,当前层Boss剩\(1\)滴血;
- 上一层Boss剩\(1\)滴血,当前层全清;
- 上一层Boss剩\(1\)滴血,当前层Boss也剩\(1\)滴血。
情况1和情况2都比较好考虑,直接从\(dp[i - 1][0]\)加过来就好了。麻烦的是情况3和4。
情况3:
要全清当前层,我们只能用min(手枪,AWP)清掉小怪,然后用AWP干掉Boss,然后转移一次,用min(手枪,激光枪,AWP)干掉上一层的Boss(因为只剩下一滴血了),然后再转移回来。需要额外转移两次。
注意,如果\(i = n\)(也就是最后一层)的话,我们就不需要再转移回来了,此时只需要额外转移一次。
情况4:
我们还可以把当前层Boss打残血,因为上一层Boss也残血,同时我们也不得不转移,所以就干脆转移到上一层,顺便把Boss干掉,然后再转移回来,把Boss也干掉。也是需要额外转移两次。
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long ll;
using namespace std;
const int maxn = 1e6 + 5;
ll n, r[4], d, a[maxn];
ll dp[maxn][2]; // dp[i][0]: All cleared; dp[i][1]: Boss alive.
inline ll min(const ll &a, const ll &b, const ll &c) {
ll t = min(a, b);
return min(t, c);
}
int main() {
scanf("%lld%lld%lld%lld%lld", &n, r, r + 1, r + 2, &d);
for (int i = 1; i <= n; i++)
scanf("%lld", a + i);
dp[1][0] = a[1] * min(r[0], r[2]) + r[2];
dp[1][1] = min(r[1], a[1] * min(r[0], r[2]) + r[0]);
for (int i = 2; i <= n; i++) {
dp[i][0] = dp[i - 1][0] + a[i] * min(r[0], r[2]) + r[2] + d;
dp[i][1] = dp[i - 1][0] + min(r[1], (a[i] + 1) * r[0]) + d;
ll tmp = dp[i - 1][1] + min(r[0], r[1], r[2]) + d;
if (i < n)
dp[i][0] = min(dp[i][0], a[i] * min(r[0], r[2]) + r[2] + d + tmp + d); // This stage cleared, but previous stage not cleared.
else
dp[i][0] = min(dp[i][0], a[i] * min(r[0], r[2]) + r[2] + d + tmp);
dp[i][0] = min(dp[i][0], min(r[1], (a[i] + 1) * r[0]) + d + tmp + d + r[0]); // This stage not cleared, and previous stage not cleared.
}
printf("%lld\n", min(dp[n][0], dp[n][1] + d * 2 + min(r[0], r[1], r[2])));
return 0;
}