Codeforces Round #666 (Div. 1)
A. Multiples of Length (CF 1396 A)
题目大意
给定一个\(n\)个数字的数组\(a\),你需要进行三次操作。
每次操作,选择一个区间,然后对区间里的每一个数,加上一个该区间长度的倍数。不同数加的数可以不同。
输出三次操作的区间以及该区间每个数所加的数。
可以证明保证有解。
解题思路
构造题。为应对任意数字的情况,我们让每个\(a_{i}\)的系数变为\(0\)。
如果\(n = 1\),那么答案就是
\(1 \space \space 1\)
\(-a_{1}\)
\(1 \space \space 1\)
\(0\)
\(1 \space \space 1\)
\(0\)
否则就
\(1 \space \space 1\)
\(-a_{1}\)
\(1 \space \space n\)
\(0 \space \space -n \times a_{2} \space \space -n \times a_{3} \space \space \dots \space \space -n \times a_{n}\)
\(2 \space \space n\)
\((n-1) \times a_{2} \space \space (n-1) \times a_{3} \space \space \dots \space \space (n-1) \times a_{n}\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int k;
cin >> k;
vector<LL> qwq;
int n = k;
while(k--){
LL a;
cin>>a;
qwq.push_back(a);
}
if (n == 1){
cout<<"1 1"<<endl;
cout<<-qwq[0]<<endl;
cout<<"1 1"<<endl;
cout<<'0'<<endl;
cout<<"1 1"<<endl;
cout<<'0'<<endl;
return 0;
}
cout<<"1 1"<<endl;
cout<<-qwq[0]<<endl;
cout<<"1 "<<n<<endl;
qwq[0] = 0;
for(size_t i = 0; i < qwq.size(); ++ i)
cout<<-(LL)n*qwq[i]<<(i == qwq.size() - 1?'\n':' ');
cout<<"2 "<<n<<endl;
for(size_t i = 1; i < qwq.size(); ++ i)
cout<<(LL)(n-1)*qwq[i]<<(i == qwq.size() - 1?'\n':' ');
return 0;
}
B. Stoned Game (CF 1396 B)
题目大意
初始\(n\)堆石子,\(T\)和\(HL\)轮流拿,\(T\)先。一个人不能拿上一轮对方拿的那堆石子。当两者均采取最优策略的情况下,谁必赢。
解题思路
博弈题。
很显然,\(n=1\)时\(T\)赢。
\(n=2\)时,若两堆石子数相同,则\(HL\)赢,否则\(T\)赢。
其余的,他们采取最优策略,即总是拿石子数最多的那堆,模拟即可得到答案。
或者可以这样考虑,因为最后是剩下两堆石子的,而如果两堆石头数量相同,则\(HL\)赢。那么,我们假设最后剩下的是第\(i\)堆和第\(j\)堆,初始\(a_{i} < a_{j}\),\(HL\)为了赢,需要花\(a_{j} - a_{i}\)轮,使得这两堆相同,剩下的取其他堆,只要此时剩下的其他堆的数量是偶数,那么最后的局面就进入\(HL\)必赢局面。
也即\(\sum\limits_{k = 1}^{n} a_{k} - a_{i} - a_{j} - (a_{j} - a_{i}) = \sum\limits_{k = 1}^{n} a_{k} - 2a_{j}\)是偶数,那么\(HL\)必赢。
由于\(2a_{j}\)时偶数,不改变结果的奇偶性,所以判断\(\sum\limits_{k = 1}^{n} a_{k}\)的奇偶性即可。
特别的,如果最大堆的数量大于其他堆数量的和,则\(T\)必赢。
综上,如果最大堆的数量大于其他堆数量的和或\(\sum\limits_{k = 1}^{n} a_{k}\)是奇数,则\(T\)必赢,否则\(HL\)必赢。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
int main(void) {
int kase; read(kase);
for (int ii = 1; ii <= kase; ii++) {
int n;
read(n);
int tot = 0;
vector<int> qwq(n);
for(int i = 0; i < n; ++ i){
read(qwq[i]);
tot += qwq[i];
}
sort(qwq.begin(),qwq.end());
if (n == 1) puts("T");
else if (n == 2){
if (qwq[0] == qwq[1]) puts("HL");
else puts("T");
}
else {
tot -= qwq[qwq.size() - 1];
if (qwq[qwq.size() - 1] > tot) puts("T");
else{
tot -= qwq[qwq.size() - 1];
if (tot & 1) puts("T");
else puts("HL");
}
}
}
return 0;
}
C. Monster Invaders (CF 1396 C)
题目大意
有\(n\)个关卡,初始第一个关卡,第\(i\)个关卡有\(a_{i}\)个\(1HP\)的普通怪物和\(1\)个\(2HP\)的\(Boss\)怪物。
你有三种枪:
- 手枪,对一只怪物造成\(1HP\)伤害,花费\(r_1\)时间
- 激光枪,对所有怪物造成\(1HP\)伤害,花费\(r_2\)时间
- AWP,直接干掉一只怪物,花费\(r_3\)时间
除了第二种,其余枪要在干掉该关卡的普通小怪后才能对\(Boss\)造成伤害。且如果对\(Boss\)造成伤害,且\(Boss\)未死时,必须转移到上一个或下一个关卡。当然,干掉完当前关卡的怪物需要手动转移到下一个关卡。
转移关卡花费\(d\)时间。
问干掉\(Boss\)需要的最短时间。
数据保证\(r_{1} \leq r_{2} \leq r_{3}\)。
解题思路
干掉一个关卡的所有怪物,我们有三种方法。
- 手枪+AWP
- 激光枪+手枪
- 手枪+手枪
第一种很简单,干完该关卡boss后就跳到下一个关卡。
关键就是第二个和第三个,它会跳到其他关卡,然后再跳回来。问题就是会往哪跳,跳多远。
首先任何往后跳的都可以看成是后来往前跳,所以方向就确定了。
然后可以猜想证明的是,最优情况下,一定是只往前跳一次。也就是来回两个关卡跳。
如果是来回三个关卡跳,需要的总时间有两个,一个是跳跃总时间,一个是干掉该层怪物所需要的时间。
后者时间是固定的,我们来看前者。
三个关卡来回跳的转移次数是6次,而三个关卡来回跳可以看成两个两个关卡的来回跳,同样是转移6次,两者时间相同。(从三个关卡最左边开始)
如果是来回四个关卡跳,则转移次数是9次,而两个两个关卡来回跳外加一次跳,转移次数是7次,后者更优。
所以我们就只用关心来回两个关卡来回跳即可。
设\(f[i]\)表示干掉前\(i\)个关卡的怪物所需要的最小时间。
首先可以直接从第\(i - 1\)个关卡转移过来,然后采用三种方法中最小的一个干掉第\(i\)个关卡的怪物。
或者从第\(i - 2\)个关卡转移过来,考虑在第\(i\)个关卡和第\(i - 1\)个关卡来回跳,分别考虑第\(i\)个关卡采用三种方式和第\(i-1\)个关卡采用的三种方式,取最小值。
注意的是如果在考虑第\(n\)个关卡时,如果采用第一种方式,然后跳到第\(n-1\)关卡时,不需要再跳回到第\(i\)个关卡。
还有就是在考虑第\(1\)个关卡和第\(2\)个关卡来回跳的时候,不要算上第\(0\)个关卡跳到第\(1\)个关卡的转移时间。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
int s = 0, c = getchar();
x = 0;
while (isspace(c)) c = getchar();
if (c == 45) s = 1, c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (s) x = -x;
}
template <typename T>
void write(T x, char c = ' ') {
int b[40], l = 0;
if (x < 0) putchar(45), x = -x;
while (x > 0) b[l++] = x % 10, x /= 10;
if (!l) putchar(48);
while (l) putchar(b[--l] | 48);
putchar(c);
}
const int N = 1e6 + 8;
LL r1, r2, r3;
LL a[N];
LL f[N];
LL d, n;
LL fuck0(int i){
return r1 * a[i] + r3;
}
LL fuck1(int i){
return r2 + r1;
}
LL fuck2(int i){
return r1 * a[i] + r1 + r1;
}
int main(void) {
read(n);
read(r1);
read(r2);
read(r3);
read(d);
for(int i = 1; i <= n; ++ i)
read(a[i]);
f[1] = fuck0(1);
for(int i = 2; i <= n; ++ i){
f[i] = min({
f[i - 1] + d + fuck0(i),
f[i - 1] + d + d + d + min(fuck1(i), fuck2(i)),
f[i - 2] + d + d + d + d * (i != n) + min(fuck1(i - 1), fuck2(i - 1)) + fuck0(i),
f[i - 2] + (i != 2) * d + d + d + d + min(fuck1(i - 1), fuck2(i - 1)) + min(fuck1(i), fuck2(i))
});
}
write(f[n], '\n');
return 0;
}