2021牛客暑期多校训练营3:B题(Black and white)【补题】
原题链接:传送门
先理解题意,这题比较特殊的是Goodeat的“技能”,使用技能的场景如下图:
对于
A
(
i
,
j
)
A(i,j)
A(i,j),此时,格子
A
(
1
,
2
)
,
A
(
1
,
4
)
,
A
(
3
,
4
)
以
及
A
(
3
,
2
)
A(1,2),A(1,4),A(3,4)以及A(3,2)
A(1,2),A(1,4),A(3,4)以及A(3,2) 即为两行两列的四个相交的正方形,而其中三个已经涂黑:
A
(
1
,
2
)
,
A
(
1
,
4
)
,
A
(
3
,
4
)
A(1,2),A(1,4),A(3,4)
A(1,2),A(1,4),A(3,4),则红色箭头标注指向的
A
(
3
,
2
)
A(3,2)
A(3,2)就可以不花费代价涂黑。
这里要注意,不花费代价的涂黑第四个格子时,对于其他三个涂黑的格子不论是使用技能还是花费代价涂黑的,只要满足使用技能的条件,就可以。
也就是说若使用技能涂黑某个格子后,这个格子又形成了一个新的四个相交的正方形且有三个涂黑了,那也是可以继续使用技能的。
回到题目本身,我们的目的是求涂黑整个棋盘的最小代价。
思考这个技能如何使用最优 ,当然了,我当场并没有想出来 。
可以证明出,对于这个权重矩阵,只要有
n
+
m
−
1
n + m - 1
n+m−1 个点涂黑了,并且满足条件:对于任意一行或任意一列都至少存在一个涂黑的点,那剩余的点都可以使用技能全部涂黑。
证明参考这篇大佬博客:传送门。
此时,这个题目的权重矩阵 A ( i , j ) A(i,j) A(i,j)可以考虑表示为边,即有 n + m n + m n+m 个点, n ∗ m n * m n∗m 条边。
至此,已经不难想到这题需要最小生成树,将权重矩阵横轴的 1~m 视为 点1~m ,竖轴的 1~n 视为点m+1~n+m,不难发现,若使用最小生成树使其联通,一定满足最低限度使用技能的条件,这个画个图就明白了,于是这题上Prim或者Kruskal就搞定了。
这是以示例一表现出来的图 1:
图 2:
注1:有些Prim的模板中,对于某个点是否已经选择的处理方式不同,有些使用的是将lowcost[i]标注为0并以此判断已经选择过。这种方式在这题不可行,因为可能会出现某条边权为0的情况,这样显然是错误的。所以对于本题应该用布尔数组(true/false)对某个点的是否选择进行处理。
注2:查阅其他人的题解时发现,本题如果用Kruskal似乎需要桶排,不然会超时,我是用Prim做的,这一点大家自行验证吧。
AC代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 1e4 + 10;
const int inf = 0x3f3f3f;
int graph[maxn][maxn];
int lowcost[maxn];
bool st[maxn];
int Prim(int n)
{
//初始化
int ans = 0;
memset(st, false, sizeof(st));
memset(lowcost, inf, sizeof(lowcost));
st[1] = true;
for (int i = 2; i <= n; i++) lowcost[i] = graph[1][i];
for (int i = 2; i <= n; i++) {
int minc = inf;
int p = -1;
//选择目前可选边中的最小权值的边,并记录连接的点
for (int j = 2; j <= n; j++) {
if (!st[j] && minc > lowcost[j]) {
minc = lowcost[j];
p = j;
}
}
ans += minc;
st[p] = true;//标记选中的点
//因为点集加入了新的点,所以需要对最小边权更新
for (int j = 2; j <= n; j++) {
if (!st[j] && lowcost[j] > graph[p][j])
lowcost[j] = graph[p][j];
}
}
return ans;
}
int main()
{
int n, m, a, b, c, d, p; cin >> n >> m >> a >> b >> c >> d >> p;
memset(graph, inf, sizeof graph);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
a = ((ll)a * a * b + (ll)a * c + d) % p;
graph[i + m][j] = graph[j][i + m] = a;
}
cout << Prim(n + m);
return 0;
}
本篇文章仅代表个人观点,如有不同意见欢迎交流。