题解:P16840 [GKS 2021 #A] Checksum
tanruiqing · · 题解
题意简化
有一个
已知每一行、每一列的异或和。
你可以花费
恢复一些格子后,剩下的未知格可以利用行、列的校验和(异或和)
补全整个矩阵,使得每一行的异或和等于
解题思路
首先我们需要理解什么情况下能使用校验和恢复。
- 对于每一行之中恰好只有一个
-1 ,此时可以用0 的花费恢复数据。 - 对于每一列之中恰好只有一个
-1 ,此时可以用0 的花费恢复数据。
因此,我们希望尽可能通过校验和免费推出未知格,而不是支付费用直接恢复。
也就是如果某一行或某一列当前只剩下一个未知格,就可以直接用该行或该列的异或和求出这个格子的值,不需要花费。
求出后,这个格子就变成已知,可能会让其他行或列也只剩下一个未知格,于是可以继续免费推出。
如果最后所有未知格都能这样被推出,那么总花费就是
我们将行、列的编号看作节点,然后对于每一个数值为
注意:为了避免行、列编号冲突,通常将列节点编号设为
一个例子:
从这张图中我们可以得出,以下位置上的数值为
(1,1)
(1,5)
(4,3)
(4,5)
(6,7)
(8,7)
可以发现,位置
将这些位置上的
导致总花费为
当我们将这些位置上的
删除完本轮可以删掉的所有边之后,就会出现新的叶子节点。
如果可以按照以上步骤将所有的边全部删除,那么总花费将会为
如果某一时刻没有可以免费推导的边了,说明图中出现了环(因为环上的节点的度数至少为
重复以上过程,直到整张图所有的边被删完,总花费就是所有被直接恢复的格子的
明显我们想要让这个总和越小越好,但是我们又不知道删除哪些边可以既剩下的边可以全部直接删除又花费最少,这样做难度很大。
所以我们正难则反,既然“删除的边的花费最小”我们求不出来,我们就用所有边的总花费减去“保留的边花费最大”,这样就好求多了!
那么这个“保留的边花费最大”如何求?因为我们需要保证这个保留下来的边组成的是一个树形(森林)结构,又要保证这些边的边权总和最大,可以想到使用最大生成树(森林)解决。
时间复杂度
:::info[AC 代码]
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e2+5;
int t;
int n;
int a[N][N];
int b[N][N];
int r[N],c[N];
struct node{
int x,y,w;
};
vector<node> e;
bool cmp(node a,node b){
return a.w>b.w; // 按边权从大到小排序。
}
int fa[2*N]; // 注意开 2*n 的空间。
// 并查集:
int get(int x){
if(fa[x]==x)return x;
return fa[x]=get(fa[x]);
}
void unite(int x,int y){
x=get(x),y=get(y);
if(x!=y)fa[x]=y;
}
int Case;
void solve(){
Case++;
e.clear(); // 注意多测。
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
int sum=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>b[i][j];
sum+=b[i][j]; // 总边权和。
if(a[i][j]==-1){ // 如果该位置为 -1,连边。
e.push_back({i,j+n,b[i][j]}); // 因为使用 (i,j) 作为边的左右端点会互相冲突,所以将其中一个节点的编号偏移 n,解决冲突的问题。
}
}
}
for(int i=1;i<=n;i++)cin>>r[i]; // R 和 C 在本题中不影响答案,因为只要图是森林就一定能推出,保证有解。
for(int i=1;i<=n;i++)cin>>c[i];
int ans=0;
for(int i=1;i<=2*n;i++)fa[i]=i; // 注意跑到 2*n。
sort(e.begin(),e.end(),cmp); // 按边权从大到小排序。
for(auto v:e){ // 最大生成树。
int x=v.x,y=v.y,w=v.w;
if(get(x)!=get(y)){
unite(x,y);
ans+=w;
}
}
cout<<"Case #"<<Case<<": "<<sum-ans<<'\n'; // 输出。
}
signed main(){
cin.tie(0)->sync_with_stdio(false);
cout.tie(0)->sync_with_stdio(false);
cin>>t;
while(t--){
solve();
}
return 0;
}
:::