freee Programming Contest 2022(AtCoder Beginner Contest 264)A-G
freee Programming Contest 2022(AtCoder Beginner Contest 264)
https://atcoder.jp/contests/abc264
A - "atcoder".substr()
输出atcoder第L位到第R位上的字符
#include <bits/stdc++.h>
using namespace std;
int main () {
string s = "atcoder";
int x, y;
cin >> x >> y;
x --, y --;
for (int i = x; i <= y; i ++) cout << s[i];
}
B - Nice Grid
我是直接嗯模拟的
也可以找规律,看奇偶性
#include <bits/stdc++.h>
using namespace std;
int a[17][17]; //黑0白1
int main () {
int x, y;
cin >> x >> y;
for (int i = 2; i <= 14; i ++) a[2][i] = a[i][2] = a[i][14] = a[14][i] = 1;
for (int i = 4; i <= 12; i ++) a[4][i] = a[i][4] = a[i][12] = a[12][i] = 1;
for (int i = 6; i <= 10; i ++) a[6][i] = a[i][6] = a[i][10] = a[10][i] = 1;
a[8][8] = 1;
// for (int i = 1; i <= 15; i ++) {
// for (int j = 1; j <= 15; j ++)
// cout << a[i][j] << ' ';
// cout << endl;
// }
if (a[x][y]) cout << "white";
else cout << "black";
}
C - Matrix Reducing
题意:可以任选A矩阵的某行或某列删去,问能否构成B矩阵
分别按行和按列看能否凑齐B矩阵
也可以暴力枚举删行 / 列
#include <bits/stdc++.h>
using namespace std;
int a[15][15], b[15][15], x, y;
int main () {
int n1, m1, n2, m2;
cin >> n1 >> m1;
for (int i = 0; i < n1; i ++) {
for (int j = 0; j < m1; j ++)
cin >> a[i][j];
}
cin >> n2 >> m2;
for (int i = 0; i < n2; i ++) {
for (int j = 0; j < m2; j ++)
cin >> b[i][j];
}
if (n2 > n1 || m2 > m1) {
cout << "No";
return 0;
}
bool suc = false;
for (int i = 0; i < (1<<n1); i++)
for (int j = 0; j < (1<<m1); j++) {
vector <int> v1, v2;
for(int k = 0; k < n1; k++) if((i & (1<<k)) == 0) v1.push_back(k);
for(int k = 0; k < m1; k++) if((j & (1<<k)) == 0) v2.push_back(k);
if (v1.size() != n2 || v2.size() != m2) continue;
//确保没有偏移
bool match = true;
for (int ii = 0; ii < n2; ii++)
for (int jj = 0; jj < m2; jj++) {
if (a[v1[ii]][v2[jj]] != b[ii][jj]) {
match = false;
break;
}
}
if (match) {
cout << "Yes";
return 0;
}
}
cout << "No";
}
//能否去掉A的某行或某列,使得AB相等
//二进制暴力枚举
D - "redocta".swap(i,i+1)
题意:给一个串,每次能交换相邻的两个字符,问最少要多少次能把他变成"atcoder"
每个字符对应一个数字,不难得出,就是把现有序列变为有序的操作次数。
那么就是经典的求逆序对问题,直接上模板
#include <bits/stdc++.h>
using namespace std;
int a[10], tmp[10];
int merge_sort (int l, int r) {
if (l >= r)
return 0;
int mid = l + r >> 1;
int res = merge_sort (l, mid) + merge_sort (mid + 1, r);
int k = 0, i = l, j = mid + 1; //计数,左半段起点,右半段起点
while (i <= mid && j <= r) {
if (a[i] <= a[j])
tmp[k ++] = a[i ++]; //有序
else {
tmp[k ++] = a[j ++];
res += mid - i + 1; //夹着的那段就是逆序对的数量
}
}
while (i <= mid) tmp[k ++] = a[i ++];
while (j <= r) tmp[k ++] = a[j ++];
for (int i = l, j = 0; i <= r; i ++, j ++)
a[i] = tmp[j];
return res;
}
int main () {
string s;
cin >> s;
for (int i = 0; i < 7; i ++) {
if (s[i] == 'a') a[i] = 1;
else if (s[i] == 't') a[i] = 2;
else if (s[i] == 'c') a[i] = 3;
else if (s[i] == 'o') a[i] = 4;
else if (s[i] == 'd') a[i] = 5;
else if (s[i] == 'e') a[i] = 6;
else if (s[i] == 'r') a[i] = 7;
}
int ans = 0;
if (is_sorted(a, a + 7)) ans = 0;
else {
ans = merge_sort (0, 6);
}
cout << ans << endl;
}
//1234567
//求逆序对数量
E - Blackout 2
题意:\(1--n\) 的点为城市,\(n+1--n+m\)的点为供电站,城市能实现供电的条件是至少连了一个发电站。现连了E条边,有q次操作,每次操作删去一条边,问此时有多少个城市能供上电
删边等于反向加边,然后把所有供电站都连接到超级源点0上(因为所有供电站都是等价的),用并查集维护连通性,以及连通块内编号即可,每次 \(cnt_0\) 就是所需答案
#include <bits/stdc++.h>
using namespace std;
const int M = 5e5 + 5, N = 2e5 + 5;
int n, m, E, q;
bool vis[M]; //表示这边需要删
int fa[N], cnt[N]; //dsu
int query[M];
vector <int> ans;
struct Node {
int u, v;
}e[M];
int find (int x) {
if (x != fa[x]) {
fa[x] = find (fa[x]);
}
return fa[x];
}
int main () {
scanf ("%d%d%d", &n, &m, &E);
for (int i = 1; i <= n; i++) fa[i] = i, cnt[i] = 1; //连通块内点的个数
for (int i = n+1; i <= n+m; i++) fa[i] = 0; //发电站均看成连到0点
for (int i = 1; i <= E; i++) {
int u, v;
scanf ("%d%d", &u, &v);
e[i] = {u, v};
}
scanf ("%d", &q);
for (int i = 0; i < q; i++) scanf ("%d", &query[i]), vis[query[i]] = true;
//最终态,也就是反向增边时的最初态
for (int i = 1; i <= E; i ++) {
if (vis[i]) continue;
int a = e[i].u, b = e[i].v;
int pa = find (a), pb = find (b);
if (pa != pb) {
if (pa == 0) swap (pa, pb); //保证cnt累加到超级源点上
fa[pa] = pb;
cnt[pb] += cnt[pa];
}
}
for (int i = q-1; i >= 0; i--) {
int j = query[i];
int a = e[j].u, b = e[j].v;
ans.push_back (cnt[0]);
int pa = find (a), pb = find (b);
if (pa != pb) {
if (pa == 0) swap (pa, pb);
fa[pa] = pb;
cnt[pb] += cnt[pa];
}
}
reverse (ans.begin(), ans.end());
for (auto i : ans) printf ("%d\n", i);
}
//删边=反向加边
F - Monochromatic Path
题意:给定大小为 \(n*m\) 的 \(01\) 矩阵,翻转行\(i\)的代价为\(r_i\), 翻转列\(j\)的代价为\(c_j\),最少操作多少次能存在一条路径,使得从 \((1,1)\) 到 \((n,m)\) 路径上颜色相同
分析:对于某行/列,操作两次等于不变,所以最多操作一次。考虑将行列的操作次数加入状态表示,则有 \(f[i][j][k][l]:\) \(i\) 行改变了 \(k\) 次, \(j\) 列改变了 \(l\) 次。
利用异或性质(\(0\bigoplus 1=1, 1\bigoplus 1=0, 0\bigoplus 0=0\))实现转移。
注意一次只能改变行/列,行变则列不变,列变则行不变
#include <bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N = 2005, inf = 1e18;
int a[N][N], r[N], c[N];
int n, m;
int f[N][N][2][2];
signed main () {
memset (f, 0x3f, sizeof f);
ios::sync_with_stdio (0);cin.tie(0);cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> r[i];
for (int i = 1; i <= m; i++) cin >> c[i];
for (int i = 1; i <= n; i++) {
string s; cin >> s;
for (int j = 1; j <= m; j++) {
a[i][j] = s[j-1] - '0';
for (auto k : {0, 1})
for (auto l : {0, 1}) {
f[i][j][k][l] = inf;
}
}
}
// for (int i = 1; i <= n; i++) {
// for (int j = 1; j <= m; j++)
// cout << a[i][j] << ' ';
// cout << endl;
// }
//init
for (auto i : {0, 1})
for (auto j : {0, 1})
f[1][1][i][j] = r[1] * i + c[1] * j;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (auto k : {0, 1})
for (auto l : {0, 1}) {
if (i < n) {
int sta = a[i][j] ^ k ^ a[i+1][j];
f[i+1][j][sta][l] = min (f[i+1][j][sta][l], f[i][j][k][l] + r[i+1] * sta);
}
if (j < m) {
int sta = a[i][j] ^ l ^ a[i][j+1];
f[i][j+1][k][sta] = min (f[i][j+1][k][sta], f[i][j][k][l] + c[j+1] * sta);
}
}
int ans = inf;
for (auto i : {0, 1})
for (auto j : {0, 1})
ans = min (ans, f[n][m][i][j]);
cout << ans << endl;
}
//f[i][j][k][l]:i行改变了k次, j列改变了l次
//往下走的时候只改变列,往右走的时候只改变行
//又是我不会的妙妙dp
G - String Fair
题意:给定 \(n\) 个长度 \(\leq3\) 的串 \(T\),每个串对应一个值 \(p_i\)。若串 \(S\) 中出现了 \(x_i\) 次 \(T_i\), 则总权值为 \(\sum_{i=1}^n x_i*p_i\)。现在你可以构造任意的串 \(S\), 问能够造出的最大权值为多少,如果是无穷就输出 \(Infinity\)
分析:因为 \(T\) 长度 \(\leq3\),所以对于 \(S\) 而言,新增一个字符带来的的权值的变化只与最后两个字符有关。故可以把 \(S\) 的最后两个字符存为状态,跑最长路即可。
#include <bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N = 27, M = N*N, inf = 1e18;
int p1[N], p2[N][N], p3[N][N][N];
int f[M], cnt[M], n, ans = -inf;
bool vis[M];
void spfa () {
queue<int>q;
for (int i = 0; i <= M; i++) f[i] = -inf;
f[M-1] = 0;
q.push(M-1);
vis[M-1] = true;
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = false;
if (x != M-1) ans = max (ans, f[x]);
if (cnt[x]++ > M + 1) { //出现循环了,无穷
cout << "Infinity\n";
return ;
}
for (int y = 0; y < 26; y++) {
int j = x/27 + y*27;
int ff = f[x] + p1[y] + p2[x/27][y] + p3[x%27][x/27][y];
if (ff > f[j]) {
f[j] = ff;
if (!vis[j]) q.push (j), vis[j] = true;
}
}
}
cout << ans << endl;
}
signed main () {
ios::sync_with_stdio (0);cin.tie(0);cout.tie(0);
cin >> n;
for (int i = 0; i < n; i++) {
string s; int m, p;
cin >> s >> p;
m = s.size();
//预处理p
if (m == 1) p1[s[0]-'a'] = p;
else if (m == 2) p2[s[0]-'a'][s[1]-'a'] = p;
else p3[s[0]-'a'][s[1]-'a'][s[2]-'a'] = p;
}
spfa ();
}
//状态: S的最后两个字符
我dp真的好烂啊。。。