Codeforces Round #767 (Div. 2)
A - Download More RAM
题目大意
给定n个内存条, 每个内存条可以增加\(b_i\)的内存, 但前提是你有足够的内存\(a_i\)去使用, 初始内存为k
思路
只要内存大于所要求的\(a_i\), 就可以使内存增加\(b_i\), 那么按照a排序贪心就好
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, k;
struct Node {
int a, b;
}a[N];
int main() {
int T;
cin >> T;
while (T --) {
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) cin >> a[i].a ;
for (int i = 1; i <= n; i ++) cin >> a[i].b;
sort(a + 1, a + n + 1, [](Node a, Node b) {
return a.a < b.a;
});
for (int i = 1; i <= n; i ++)
if (a[i].a <= k) {
k += a[i].b;
}
cout << k << endl;
}
return 0;
}
B - GCD Arrays
题目大意
给定一个去区间[l, r], 问经过k次操作之后, 能不能使所有数的最大公约数不为1
每次操作为删除两个数, 并将这两个数的乘积加入数组
思路
区间[l, r], 一串相连的数字, 那么他们的最大公约数除了1, 只能是2了, 又或者所有数合成了一个
- 合成一个, 需要至少
r - l + 1
次, 这里有个特例就是左右端点相等, 那这两个端点就不能为1了 - 最大公约数为2, 必须得相邻两个相互配对, 而且如果右端点为奇数的话, 那么右端点就必须和左边的那一组配对
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, k;
struct Node {
int a, b;
}a[N];
int main() {
int T;
cin >> T;
while (T --) {
int a, b, c;
cin >> a >> b >> c;
if (a == b && a > 1) puts("YES");
else if ((b - a + 1 + (b % 2)) / 2 <= c) puts("YES");
else puts("NO");
}
return 0;
}
C - Meximum Array
题目大意
每次可以从头开始, 选取任意长度的原数组, 然后将这段序列的MEX
加入新数组中, 问最后形成的新数组最大是多少
思路
首先原数组是一定要被遍历完的
- 新数组最大, 一定是先将所有数可以凑出的最大的MEX加入
- 然后从0到n, 一旦遇到断点, 就将断点加入答案
- 如果是0缺失的话, 应该是加入剩下原数组长度的0加入答案
然后是怎么样遍历
我们可以枚举当前的MEX, 如果后面存在这个数, 那么MEX++, 如果不存在, 那么这个MEX就是断点
预处理每个数的位置, 利用数组下标i来判断是否是否枚举完毕
代码
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
const int N = 2e5 + 10;
int n;
int a[N], b[N];
vector<int> idx[N];
vector<int> ans;
int main() {
int T;
cin >> T;
while (T --) {
cin >> n;
ans.clear();
for (int i = 0; i <= n; i ++) {
idx[i].clear();
b[i] = 0;
}
for (int i = 1; i <= n; i ++) {
cin >> a[i];
idx[a[i]].push_back(i);
}
int cnt = 0, i = 1;
while (i <= n) { // 当没有枚举到数组末尾的时候
//cout << ans.size() << ' ' << cnt << ' ' << i << endl;
if (b[cnt] < idx[cnt].size()) { // 如果当前的MEX在数组后面还有
i = max(i, idx[cnt][b[cnt]]); // 将数组下标后移
b[cnt ++] ++;
if (cnt > n) {
for (int j = 0; j < cnt; j ++) // 所有数组下标之前的数, 都不能再进行访问
while (b[j] < idx[j].size() && idx[j][b[j]] <= i) b[j] ++;
ans.push_back(cnt);
cnt = 0;
}
}
else {
if (cnt == 0) { // 如果缺失的是0
while (i <= n) {
ans.push_back(0);
i ++;
}
break;
}
else { // 不是0, 缺失一个断点, 重新进行遍历
ans.push_back(cnt);
for (int j = 0; j < cnt; j ++)
while (b[j] < idx[j].size() && idx[j][b[j]] <= i) b[j] ++;
cnt = 0;
if (i == n) break; // 数组已经到末尾就退出, 没有到就++, 继续进行下一轮的遍历
else i ++;
}
}
}
cout << ans.size() << endl;
for (auto j : ans) cout << j << ' '; cout << endl;
}
return 0;
}
D - Peculiar Movie Preferences
题目大意
给定n个长度不超过3的字符串, 问是否可以选取任意多个字符串来构成一个回文字符串
思路
主要是有长度不超过3这个限制, 这样我们就可以进行暴力的枚举, 回文串的长度无非是1, 2, 3, 4, 5, 6, (如果有更长的回文串的话, 我们一定可以删去其中的一部分)
- 长度为1的字符串, 一定就是回文串了
- 长度为2的字符串, 要么本身是回文, 要么是存在一个字符串正好相反
- 长度为3的字符串, 第一种还是本身是回文, 第二种是存在一个相反的字符串, 第三种是存在一个长度为2的字符串, 可以拼接到后面或者前面形成一个回文,
第四种拼一个长度1的, 都有长度为1的了, 干嘛还需要组合
代码
#include <iostream>
#include <map>
#include <vector>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
int n;
map<string, vector<int> > mp;
bool solve() {
mp.clear();
cin >> n;
for (int i = 1; i <= n; i ++ ) {
string x;
cin >> x;
mp[x].push_back(i);
}
for (auto i : mp) {
if (i.first.size() == 1) {
return true;
}
else if (i.first.size() == 2) {
string x = i.first;
reverse(x.begin(), x.end());
if (mp.count(x)) return true;
}
else {
string x = i.first.substr(0, 2); // 拼接到这个后面
string y = i.first.substr(1, 2); // 拼接到这个前面
string z = i.first;
reverse(x.begin(), x.end());
reverse(y.begin(), y.end());
reverse(z.begin(), z.end());
if (mp.count(x) && mp[x].back() > i.second.front()) return true;
if (mp.count(y) && mp[y].front() < i.second.back()) return true;
if (mp.count(z)) return true;
}
}
return false;
}
int main() {
int T;
cin >> T;
while (T --) {
if (solve()) puts("YES");
else puts("NO");
}
}
E - Grid Xor
题目大意
给定一个n \(\cdot\) n的矩阵,矩阵中点(i,j)的值为其四周的格子的异或值组成,要我们求所有格子的的数的异或值。保证n是偶数
思路
我们通过模拟4 \(\cdot\) 4的发现一定是两个两个选的,且不能选会覆盖之前已经选了的区域。根据题目中可以证明解一定唯一,我们得知解一定存在,又由于是正方形,因此我们一行一行的选取格子并将其覆盖区域染色,时刻保证:
- 前面的空格不被遗漏
- 后面新选取的格子覆盖的区域与之前无重叠
因为有重叠了等于没染,此时又要新的格子来染被消除的,会导致新的格子被消除。
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;
int n;
int a[N][N];
bool st[N][N];
int dx[] = { 0, 0, 1, -1 };
int dy[] = { 1, -1, 0, 0 };
int main() {
int T;
cin >> T;
while (T --) {
memset(st, 0, sizeof st);
cin >> n;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> a[i][j];
int ans = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ ) {
bool f = false;
for (int k = 0; k < 4; k ++) { //先判断四周有没有被覆盖到
int tx = i + dx[k], ty = j + dy[k];
if (st[tx][ty]) {
f = true;
}
}
if (!f) { //如果没有被覆盖到,则选择
ans ^= a[i][j];
for (int k = 0; k < 4; k ++) { //将四周覆盖
int tx = i + dx[k], ty = j + dy[k];
st[tx][ty] = true;
}
st[i][j] = true;
}
}
cout << ans << endl;
}
return 0;
}
F - Game on Sum
题目大意
n轮游戏,每轮一个数,可以加上或减去,但至少加上的轮数要是m轮,每个数的范围为[0,k]的实数,问最终得分多少
思路
动态规划
f[i][j]
表示当前进行了i轮, 进行过j次加法的最小得分
则f[i][j] = min(x + f[i - 1][j - 1], -x + f[i - 1][j]), x \(\in\) [0, k]
x要在[0, k]之间枚举还是要超时, 显然取中间值时是最优选择, 上式可以优化为f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) / 2
这里要注意就是n == m
的情况.
\(\Downarrow\)
观察上面的状态转移方程:f[i][j] = (f[i − 1][j] + f[i − 1][j − 1]) / 2,需要求的是f[n][m],如果不考虑除以2,是不是很像路径方案数的状态转移方程?
的确是这样,与(1, 1)到(n, m)的方案数求法的一点区别不同就是初始值的不同,f[i][i] = i ∗ k(由F1可知),而从(i, j)到(n, m)的方案数为\(C\binom{n - 1}{m - 1}\)。我们考虑从(i, i)到(n, m)的贡献值即可,贡献值为\(C\binom{n - i - 1}{m - 1} \cdot f[i][i] \cdot \frac{1}{2^{n - i}}\)(因为中间状态转移时被除了n − i个2),将这些贡献值加起来即可
书的边角太小以至于写不下我的证明
代码1(动态规划, 只能完成F1)
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 2010, mod = 1e9 + 7;
int n, m, k;
LL f[N][N];
LL qmi(LL a, LL b) {
LL ans = 1;
while (b) {
if (b & 1) ans = (ans * a) % mod;
b >>= 1;
a = (a * a) % mod;
}
return ans;
}
int main() {
int T;
cin >> T;
while (T --) {
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++ ) {
f[i][0] = 0;
f[i][i] = k * 1ll * i % mod;
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++)
if (i != j)
f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) * qmi(2, mod - 2) % mod;
cout << f[n][m] << endl;
}
return 0;
}
代码2(数论)
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10, mod = 1e9 + 7, inv = 500000004; // inv = qmi(2, mod - 2)
int n, m, k;
LL fac[N], infac[N];
LL qmi(LL a, LL b) {
LL ans = 1;
while (b) {
if (b & 1) ans = (ans * a) % mod;
b >>= 1;
a = (a * a) % mod;
}
return ans;
}
LL C(LL a, LL b) {
return fac[a] * infac[b] % mod * infac[a - b] % mod;
}
int main() {
fac[0] = infac[0] = 1;
for (int i = 1; i < N; i ++ ) {
fac[i] = fac[i - 1] * i % mod;
infac[i] = qmi(fac[i], mod - 2);
}
int T;
cin >> T;
while (T --) {
cin >> n >> m >> k;
if (n == m) cout << n * 1ll * k % mod << endl;
else {
LL ans = 0;
for (int i = 1; i <= m; i ++ )
ans = (ans + i * 1ll * C(n - i - 1, m - i) % mod * qmi(inv, n - i)) % mod;
cout << ans * k % mod << endl;
}
}
return 0;
}
AK之后的颜色竟然是#D4EDC9