Codeforces Round #766 (Div. 2)
A - Not Shading
题目大意
每次选择一个黑格子, 可以将同行或者同列的格子全部变为黑色, 问最少需要多少次可以将目标格子涂成黑色
思路
- 不存在黑格子, 那么就是不能涂成黑色, -1
- 本来就是黑格子, 0次
- 同行或者同列, 1次
- 不同行且不同列, 那么最多只需要两次啦, 这个常识
代码
#include <iostream>
using namespace std;
const int N = 60;
int n, m, x, y;
char g[N][N];
int main() {
int T;
cin >> T;
while (T --) {
cin >> n >> m >> x >> y;
for (int i = 1; i <= n; i ++) cin >> (g[i] + 1);
bool f = false;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++ ) {
if (g[i][j] == 'B') {
f = true;
break;
}
}
}
if (!f) {
puts("-1");
continue;
}
if (g[x][y] == 'B') puts("0");
else {
for (int i = 1; i <= max(n, m); i ++) {
if (i <= n && g[i][y] == 'B') {
puts("1");
f = false;
break;
}
if (i <= m && g[x][i] == 'B') {
puts("1");
f = false;
break;
}
}
if (f) puts("2");
}
}
return 0;
}
B - Not Sitting
题目大意
凳子分为两种, 没有颜色和粉色, 有两个人(以下简称A和B), 开始时A可以将k
个凳子涂成粉色, B不坐粉色的凳子, A想要尽可能远离B, 而B想要尽可能靠近A, 问在k
分别取0到n* m - 1的情况下, A和B之间的最大距离是多少
思路
B每次都会选尽可能靠近中间的座位,而A每次会选四个角落中的某个。但是我们要是这样单纯的想其实很难模拟出来涂色的规律。我们不妨倒着想,对于每一个被涂色的座位,总是被涂色前B的最佳选择,因为他们的选择都是明智的,也就是说我们可以将被涂色的座位作为B的位置。因为每个位置都会被涂色,所以每个位置都会作为B的座位。这样问题就简化为每个位置距离四个角落的最大值,我们将他们放到一个数组中,然后排序输出即可,因为k越大时越有利于A同学,两者的距离也就越大。
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 60;
int n, m;
vector<int> a;
int main() {
int T;
cin >> T;
while (T --) {
a.clear();
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
a.push_back(max(i - 1, n - i) + max(j - 1, m - j));
sort(a.begin(), a.end());
for (int i = 0; i < n * m; i ++ ) cout << a[i] << ' '; cout << endl;
}
return 0;
}
C - Not Assigning
题目大意
要构建一棵树, 树的边已经给出, 要求树的每条边的权值是素数, 相邻两条边的和也是素数, 要求按照顺序输出权值
思路
素数: 2, 3, 5, 7, 11, 13, 17, 19, 21...
除2以外所有的素数都是奇数, 也就是说在树上时不能相邻, 这样的话, 树上就不能有一个节点的度大于3, 树必须退化为一条链
而这样被2分割开之后发现, 不管选哪一个素数都一样, 所以我们只要让2和任意一个素数轮流出现就可以了
代码
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n;
int d[N];
bool st[N];
int h[N], e[N * 2], ne[N * 2], idx;
PII edge[N];
map<PII, int> mp;
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
void dfs(int u, int fa, int c) {
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (j == fa || st[j]) continue;
st[j] = true;
mp[{u, j}] = mp[{j, u}] = c;
dfs(j, u, 7 - c);
}
}
int main() {
int T;
cin >> T;
while (T --) {
idx = 0;
memset(st, false, sizeof st);
memset(h, -1, sizeof h);
memset(d, 0, sizeof d);
cin >> n;
bool f = false;
for (int i = 1; i < n; i ++) {
int a, b;
cin >> a >> b;
if (a > b) swap(a, b);
d[a] ++;
if (d[a] > 2) f = true;
d[b] ++;
if (d[b] > 2) f = true;
add(a, b);
add(b, a);
edge[i] = { a, b };
}
if (f) {
puts("-1");
}
else {
for (int i = 1 ; i <= n; i ++)
if (d[i] == 1)
dfs(i, -1, 2);
for (int i = 1; i < n; i ++) {
cout << mp[edge[i]] << ' ';
}
cout << endl;
}
}
return 0;
}
D. Not Adding
题目大意
每次选取两个数, 如果他们的gcd不在数组中, 就将他们的gcd添加到数组中去, 问能这样操作多少次
思路
这题我们最终是看有多少个gcd可以被加在数组后面,因为gcd只会变小不会变大,所以我们最多将1~A中的所有数都加进来(A是整个数组元素的最大值)。所以我们可以考虑对于暴力1到A的每一个数,看这个数(下面记为x)能否由数组中的gcd凑出来。一共只有两种情况:
- x本来就是数组元素的一个数
- x是多个数组元素的gcd
对于以上两种情况,我们可以用筛法的思想,对于每个x我们枚举x的倍数y,如果y在数组元素中,我们对其做一次gcd,如果最后的所有的y的gcd就是x,那么说明x一定是可以被凑出来的,且这样不会漏掉某些答案。(因为x的倍数一定是含有因子x的,所以他们的gcd最多只会比x大,比如x,2x,3x等。若x本身就是数组元素,那么gcd直接就是x,;若x是由某些子元素gcd合成的那么一定是最小的gcd,而更大的x会在后面遍历到)
代码
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
bool st[N];
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int main() {
cin >> n;
int maxv = 0;
for (int i = 1; i <= n; i ++) {
int x;
cin >> x;
st[x] = true;
maxv = max(maxv, x);
}
int cnt = 0;
for (int i = 1; i <= maxv; i ++ ) {
int div = 0;
for (int j = 1; j * i <= maxv; j ++ )
if (st[j * i]) div = gcd(div, i * j);
if (i == div) cnt ++;
}
cout << cnt - n << endl;
return 0;
}
E - Not Escaping
题目大意
从(1, 1) 一直到(n, m), 每次可以在每个楼层横向移动, 权值为x[i] * abs(i - j), 也可以借助梯子爬行, 每次权值为\(h_i\)
思路
直接存一个二维矩阵是肯定不行的, 于是选择离散化所有点, 毕竟梯子只有1e5, 点数最多就是2e5 + 2(起点和终点)
- 对于每一层来说, 可以从左边或者右边横向移动
- 纵向移动就通过梯子
\(f_i\)表示到离散化后点i的最小花费 - 从每一层开始枚举, 同一层内可以从任意点到达, 但是从A到B必须经过A到B中间的每一个点, 这里是一个小的dp, 从该层的最左边和最右边分别走, 可以保证每一层的移动最小
- 然后枚举该层的所有梯子, 注意点的离散化
代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
#include <vector>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int n, m, k, cnt;
int x[N];
LL f[N];
vector<PII> row[N];
PII edge[N];
int main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int T;
cin >> T;
while (T --) {
cnt = 0;
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++ ) cin >> x[i], row[i].clear();
for (int i = 1; i <= 2 * k + 3; i ++ ) f[i] = 1e18, edge[i] = {0, 0};
row[1].push_back({ 1, ++ cnt });
for (int i = 1; i <= k; i ++ ) {
int a, b, c, d, h;
cin >> a >> b >> c >> d >> h;
row[a].push_back({ b, ++ cnt });
row[c].push_back({d, ++ cnt});
edge[cnt - 1] = { cnt, -h };
}
row[n].push_back({ m, ++ cnt });
f[1] = 0;
for (int i = 1; i <= n; i ++ ) {
sort(row[i].begin(), row[i].end());
for (int j = 1; j < row[i].size(); j++)
f[row[i][j].y] = min(f[row[i][j].y], f[row[i][j - 1].y] + 1LL * x[i] * (row[i][j].x - row[i][j - 1].x));
for (int j = row[i].size() - 2; j >= 0; j--)
f[row[i][j].y] = min(f[row[i][j].y], f[row[i][j + 1].y] + 1LL * x[i] * (row[i][j + 1].x - row[i][j].x));
for (auto j : row[i])
if (edge[j.y].x && f[j.y] != 1e18)
f[edge[j.y].x] = min(f[edge[j.y].x], f[j.y] + edge[j.y].y);
}
//for (int i = 1; i <= cnt; i ++ ) cout << f[i] << ' '; cout << endl;
if (f[cnt] == 1e18) cout << "NO ESCAPE" << endl;
else cout << f[cnt] << endl;
}
return 0;
}