T2 立体几何
第一次遇到这么可爱的出题人,其实这道题和二分图匹配没有半毛钱关系。
一定要看到最后,看蒟蒻第一次hack标程。
Background
小 P 是一个热爱几何的男孩子。
Description
OI 退役滚去高考的小 P 最近复习到了数学必修二第一章。他开始研究空间几何体 的三视图。他拿出了若干个大小相同的正方体小方块,将它们整齐地堆在一个 n × m 的 网格中,并且记录下了所形成立体图形的三视图。现在小 P 想要考考你,最多能取走 多少个小方块,使得存在一种重新安排小方块的方案,让三视图均不发生改变呢?
Format
Input
第一行一个正整数 T,表示数据组数。
接下来 T 行,每行两个正整数 n 和 m,分别表示网格的行数和列数。接下来 n 行, 每行 m 个整数 {ai, j},表示对应位置上小方块的数目。
Output
对于每组数据,输出一行一个整数,表示最多能取走的小方块数量。
Samples
输入数据1
2
5 5
1 4 0 5 2
2 1 2 0 1
0 2 3 4 4
0 3 0 3 1
1 2 2 1 1
2 3
50 20 3
20 10 3
输出数据1
9
30
输入数据2
见附件view2.in
输入数据2
见附件view2.ans
题解
原题解是这么说的:
• 好像数据造水了导致一堆不正确的做法获得了高分
• 锅++
• 子任务全是瞎放的
• 首先要满足俯视图,我们可以把所有有小方块的位置减到只剩一个
• 不难发现侧视与正视图上看到的是对应最高的小方块高度
• 若某行和某列最大值一样,我们可以只在他们相交的位置放置对应数量的小
方块
• 匈牙利算法跑二分图匹配即可
一开始看不懂没事,可以先看下面蒟蒻自己的做法,没有用二分图匹配,更简单一点。
看完下面的代码先别走,一定要看到最后,看蒟蒻第一次hack标程。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 102;
int a[maxn][maxn],d[maxn],n,m,hm[maxn],sm[maxn];
bool vis[maxn];
LL ans = 0;
bool dfs(int s){
if(vis[s]) return 0;
vis[s] = 1;
for(int v = 1; v <= m; v++)
if(a[s][v] && hm[s] == sm[v])
if(!d[v] || dfs(d[v])){
d[v] = s; return 1;
}
return 0;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
ans = 0;
scanf("%d%d",&n,&m);
memset(d,0,sizeof(d));
memset(hm,0,sizeof(hm));
memset(sm,0,sizeof(sm));
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++){
scanf("%d", &a[i][j]);
hm[i] = max(hm[i],a[i][j]);
sm[j] = max(sm[j],a[i][j]);
if(a[i][j] > 1) ans += a[i][j] - 1;
}
for(int i = 1;i <= n;i++)
if(hm[i] > 1)
ans -= hm[i] - 1;
for(int i = 1;i <= m;i++)
if(sm[i] > 1)
ans -= sm[i] - 1;
for(int i = 1; i <= n; i++){
memset(vis,0,sizeof(vis));
if(dfs(i))
if(hm[i] > 1)
ans += hm[i] - 1;
}
printf("%lld\n", ans);
}
return 0;
}
先解释一下原题解的意思。
我们先把所有方块取走,直至原来有方块的格子只剩下一个(保证俯视图)。
然而这时,我们的正视图以及侧视图与原来都不同了,于是我们需要补回去一些方块。
因为正视图侧视图看到的是纵向和横向的方块数最大值,我们只需要在读入时处理一下:“每个点横向与纵向方向上的方块数最大值”,分别用
h
m
hm
hm和
s
m
sm
sm数组记录。
(
h
m
−
>
横
m
a
x
,
s
m
−
>
竖
m
a
x
)
(hm->横max,sm->竖max)
(hm−>横max,sm−>竖max)
在补方块时,我们只需要把每一行每一列的最大值补一次就行了。
然而,这不是最优的方案。因为在最开始,有可能一个点同时管辖了“横行与纵列上的方块数最大值”。即假设一个点的坐标是
(
i
,
j
)
(i,j)
(i,j),
h
m
[
i
]
=
s
m
[
j
]
hm[i] = sm[j]
hm[i]=sm[j]。
这时候我们就需要根据容斥原理,减去一次多补的方块。至于怎么减,原题目给了匈牙利算法做法。
之所以能把这个看做二分图匹配,原因在于,横行与纵列的方块数最大值聚集在一点的点就那么多个,在最优方案中,每个点就只有一个管辖横行或纵列的可能,我们只需要用横行最大或者纵列最大去匹配这些特别的点。
但 是!真的需要那么做吗???
一、怎么就需要二分图匹配了???绕那么大弯子干嘛。
二、最后的匈牙利跑二分图并不需要,这道题就是个简单的容斥。在最后计算行列最大值聚集在一个点上的情况时,只需要
O
(
n
2
)
O(n^2)
O(n2)地扫一遍就好了,扫到这种点就清空存储最大值的数组,在有些数据上比原题解程序还快(亲测)。
三、第一次见到这么可爱的出题人。
下面是我自己的最终程序:
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 102;
int a[maxn][maxn],d[maxn],n,m,hm[maxn],sm[maxn];
bool vis[maxn];
LL ans = 0;
int main(){
int T;
scanf("%d",&T);
while(T--){
ans = 0;
scanf("%d%d",&n,&m);
memset(d,0,sizeof(d));
memset(hm,0,sizeof(hm));
memset(sm,0,sizeof(sm));
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++){
scanf("%d", &a[i][j]);
hm[i] = max(hm[i],a[i][j]);
sm[j] = max(sm[j],a[i][j]);
if(a[i][j] > 1) ans += a[i][j] - 1;
}
for(int i = 1;i <= n;i++)
if(hm[i] > 1)
ans -= hm[i] - 1;
for(int i = 1;i <= m;i++)
if(sm[i] > 1)
ans -= sm[i] - 1;
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
if(a[i][j] == 0) continue;
if(hm[i] == sm[j] && hm[i] > 0){
ans += hm[i] - 1;
hm[i] = 0;
sm[j] = 0;
}
}
}
printf("%lld\n", ans);
}
return 0;
}
在附一张hack前后对比图:
标程测评记录:
hack后,蒟蒻的测评记录:
也不能说快得多了,反正是快了点,至少思路简单的多了。
看蒟蒻hack地那么辛苦。点个赞再走呗 Q A Q QAQ QAQ