[Kick Start] 2021 Round A
题目:2021 Round-A .
K-Goodness String
签到题,计算当前字符串的 K-Goodness Score ,然后与给出的 K 做差即可。
#include <iostream>
#include <string>
using namespace std;
int cnt = 1;
int solve(const string &s, int n, int k)
{
int cur = 0;
for (int i = 0; i < n / 2; i++) cur += s[i] != (s[n - i - 1]);
return abs(k - cur);
}
int main()
{
int t, n, k;
string str;
cin >> t;
while (t--)
{
cin >> n >> k;
cin.ignore();
cin >> str;
cin.ignore();
printf("Case #%d: %d\n", cnt++, solve(str, n, k));
}
}
L Shaped Plots
L 字要求长边的长度是短边的 2 倍。短边长度至少为 2 ,长边长度至少为 4 。
枚举每个 matrix[i, j] == 1
的点(把该位置视为 L 的拐点)求出其上下左右四个方向的 1 的长度,通过 {up, down, left, right}
4 个变量记录。
然后对于 4 个方向,把该方向作为短边尝试一次,计算以该方向作为短边的 L 的个数。
以下面输入为例子:
1 0 0 0
1 0 0 1
1 1 1 1
1 0 1 0
1 0 1 0
1 1 1 0
在位置 (2, 2)
上,其 4 个方向的 1 的长度为 {up=1, down=4, left=3, right=1}
,那么:
up = 1
,该方向不能为短边。down = 4
,以该方向为短边时,那么长边必然是left
或者right
。 在down
方向上,短边长度可以为 2, 3, 4, 但left, right
作为长边均不满「长边长度至少 4 的要求」。left = 3
,以该方向作为短边,短边长度可以为 2 或者 3,当为 2 时,可以与down
组成 1 个 L 字。right = 2
,以该方向作为短边,长度为 2 ,可以与down
组成 1 个 L 字。
因此在位置 (2, 2)
上共有 2 个 L 字。
时间复杂度 \(O(n^3)\) .
#include <iostream>
#include <vector>
#include <array>
using namespace std;
#define MAXN 1001
int nums[MAXN][MAXN] = {{0}};
int cnt = 1;
int rows, cols;
array<int, 4> getFourLength(int i, int j)
{
int up, down, left, right;
up = down = i, left = right = j;
while (up >= 0 && nums[up][j]) up--;
while (down < rows && nums[down][j]) down++;
while (left >= 0 && nums[i][left]) left--;
while (right < cols && nums[i][right]) right++;
return array<int, 4>({i - up, down - i, j - left, right - j});
}
int solve()
{
int ans = 0;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
if (nums[i][j] == 0) continue;
auto [up, down, left, right] = getFourLength(i, j);
auto calc = [&ans](int shortedge, int longedge1, int longedge2) {
for (int d = 2; d <= shortedge; d++)
ans += (longedge1 >= 2*d), ans += (longedge2 >= 2*d);
};
calc(up, left, right), calc(down, left, right);
calc(left, up, down), calc(right, up, down);
}
}
return ans;
}
int main()
{
int t;
cin >> t;
while (t--)
{
cin >> rows >> cols;
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
cin >> nums[i][j];
printf("Case #%d: %d\n", cnt++, solve());
}
}
Rabbit House
题目的意思就是说,每个「极高点」(盗用了函数中的极值点的概念),其周围 4 个方向的点都必须与它等高,或者比它高度小 1。
如果只有一个最高点,那么是很简单的(一次 BFS 即可完成),但这里可能会出现多个最高点。
这里我们采用堆思想,从堆顶得到最高点,观察其 4 个相邻点的高度,判断是否需要填充高度。若需要,那么更新这个相邻点的高度(同时记录增加了多少高度),并把这个相邻点也放入堆中。
代码实现用了 set
取替代堆了,反正我们只要能在 \(O(1)\) 时间内得到最高点即可。
时间复杂度 \(O(n\log{n}), n=rc\) .
#include <iostream>
#include <vector>
#include <set>
using namespace std;
#define N 301
int t, rows, cols;
int graph[N][N] = {{0}};
struct node_t
{
int x, y, height;
node_t(int xx = -1, int yy = -1, int hh = 0) : x(xx), y(yy), height(hh) {}
bool operator<(const node_t &n) const
{ return height > n.height || (height == n.height && (x < n.x || (x == n.x && y < n.y))); }
};
uint64_t solve(set<node_t> &s)
{
uint64_t ans = 0;
static const vector<pair<int, int>> dirs = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
while (!s.empty())
{
auto [x, y, height] = *s.begin();
s.erase(s.begin());
for (auto &d : dirs)
{
int dx = x + d.first, dy = y + d.second;
if (dx < 0 || dx >= rows || dy < 0 || dy >= cols) continue;
if (graph[dx][dy] <= height - 2)
{
ans += height - 1 - graph[dx][dy];
s.erase(node_t(dx, dy, graph[dx][dy]));
graph[dx][dy] = height - 1;
s.emplace(dx, dy, graph[dx][dy]);
}
}
}
return ans;
}
int main()
{
cin >> t;
for (int k = 1; k <= t; k++)
{
cin >> rows >> cols;
set<node_t> s;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
cin >> graph[i][j];
s.emplace(i, j, graph[i][j]);
}
}
printf("Case #%d: %llu\n", k, solve(s));
}
}
Checksum
一个重要结论:如果某行/某列,只有一个元素被损坏,那么是可以通过该行/该列的校验和进行还原的,这种情况下不需要任何的 cost
.
我们把每行,每列都看作一个点,如果位置 (i, j)
为 -1 ,那么就把 row[i]
和 col[j]
连接起来,这样会得到一个二分图。
对于度为 1 的点,说明该行/列,只有 1 个 -1,那么这个数据可以通过检验和还原,不需要任何的 cost
,我们在图中去掉所有这样的边,并且去掉之后,产生的新的度为 1 的点也需要同样的操作。
上述的性质表明,如果得到图是一颗树,那么所有的数据都能通过校验和还原。
比如下图的边 (row[1], col[2])
和 (row[4], col[1])
就是这样的边。
对于剩下的边所组成的图中,我们要去掉一些边,然后令整个图成为一棵树。显然,去除 (row[i], col[j])
这条边时,需要耗费 B[i, j]
。
我们的目的是,去除权值最小的边,得到图的一棵树。换而言之,就是求出图的最大生成树 tree
。那么去除边所使用的权值就是 sum(graph) - sum(tree)
.
代码实现
#include <iostream>
#include <unordered_map>
#include <vector>
#include <queue>
using namespace std;
#define N 502
#define getrowid(r) (r)
#define getcolid(c) (n + c)
int nt;
int A[N][N] = {{0}}, B[N][N] = {{0}};
int R[N] = {0}, C[N] = {0};
struct node_t
{
int x, y, cost;
node_t(int xx, int yy, int cc) : x(xx), y(yy), cost(cc) {}
bool operator<(const node_t &node) const { return cost < node.cost; }
};
int find(vector<int> &root, int x) { return root[x] == -1 ? x : root[x] = find(root, root[x]); }
uint64_t solve(int n)
{
unordered_map<int, unordered_map<int, int>> g;
vector<int> degree(2 * n + 1, 0);
int rid, cid;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (A[i][j] == -1)
{
rid = getrowid(i), cid = getcolid(j);
g[rid][cid] = g[cid][rid] = B[i][j];
degree[rid]++, degree[cid]++;
}
}
}
// 去除度为 1 的点和这条边
for (int i = 1; i <= 2 * n; i++)
{
if (degree[i] == 1)
{
int vex = g[i].begin()->first;
degree[i]--, degree[vex]--;
g.erase(i);
if (g.count(vex))
{
g[vex].erase(i);
if (g[vex].empty()) g.erase(vex);
}
}
}
// 使用 并查集 + Kruskal 求最大生成树
priority_queue<node_t> q;
vector<int> root(2 * n + 1, -1);
uint64_t sum = 0;
for (auto &[x, m] : g)
for (auto [y, c] : m)
q.emplace(x, y, c), sum += c;
// g 是邻接矩阵,里面有重复的边
sum /= 2;
// cost 计算最大生成树
uint64_t cost = 0;
while (!q.empty())
{
auto [x, y, c] = q.top();
q.pop();
x = find(root, x), y = find(root, y);
if (x != y)
{
root[y] = x;
cost += c;
}
}
return sum - cost;
}
int main()
{
cin >> nt;
int n;
for (int t = 1; t <= nt; t++)
{
cin >> n;
uint64_t sum = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> A[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> B[i][j];
for (int j = 1; j <= n; j++) cin >> R[j];
for (int j = 1; j <= n; j++) cin >> C[j];
printf("Case #%d: %llu\n", t, solve(n));
}
}