问题描述:
有一个箱子堆,里面有很多的箱子,为了防止有人偷,放置了3个感应报警装置,分别对应空间中x, y, z三维。具体如下:
(图1-图4,图1给出每个位置箱子的高度,图2为从前向后的投影,图3为从左向右的投影,图4为从上向下的投影)
如果三个方向的投影有任何变化,报警装置就会开始工作(报警)。同时也留下了可以钻的空子。我们可以偷走那些对于投影没有影响的箱子。
给出每个位置上箱子的高度,问最多可以偷走多少个箱子。以图1给出的数据为例,下图的投影和图1相同:
可以将箱子移动到其他位置,以保证投影不变。
输入
第1行:2个数m, n 中间用空格分割,分别表示行数和列数(1 <= m, n <= 100)。
第2 - m + 1行:每行n个数,对应所在位置箱子的数量a(i,j)(1 <= a(i,j) <= 1e9)。
输出
输出共1个数,对应最多可以偷走多少个箱子。
输入样例
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
输出样例
9
题解:
这题我想简单了,以为贪个心就能过的orz,没想到横纵之间的冲突完全没考虑到,还是受某Acmer指导才及时搞出来。
首先我们得到了这个图的俯视图,因为监控是三个维度都有的,所以我们一行一行、一列一列地来考虑这个问题。
对于第一行,1 4 0 5 2,它的俯视图应该是:
▇▇ ▇▇ 口 ▇▇ ▇▇
考虑到上方的监控,我们可以将有箱子的地方都偷剩1个,也就是成为 1 1 0 1 1,但是此时左右方向的监控就该报警了。
假如前视图是这个样子:
▇▇
sad▇▇
sad▇▇
sad▇▇
sad▇▇
sadsadsad▇▇
sadsadsad▇▇
sadsadsad▇▇
sadsadsad▇▇
sadsadsad▇▇
sadsadsadsad▇▇
sadsadsadsad▇▇
这个状态下就太惨了,因为这些箱子在左右方向的覆盖面太大了,所以你一个箱子都偷不了。但是如果我们提前把箱子挪成这样:
▇▇ ▇▇ sad ▇▇ ▇▇
sad▇▇ sad ▇▇ ▇▇
sad▇▇ sad ▇▇ sad
sad▇▇ sad ▇▇ sad
sadsadsad▇▇ sad
这样的话它的跨度只有max(h1,h2…,hn),所以为了维持左右方向的投影,我们只需要返还max(h1,h2,…hn)-1个箱子。
纵向同理,按照相同的步骤再操作一遍即可。
然后我就交了,最后连用例都没过去orz
这是怎么回事呢?
原因是可能存在一行有多个点同时取到了最大值,并且该行地一点所在列的最大值也和这行的最大值相等。那么我们选择这个在行、列交叉处的点来维持投影,其实际需要返还的箱子数一定比 保持该行另一点和那列最大值那点不变所返还箱子数的数量更少。
例如:
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
常规操作下,我们的行为是对这一行和对应的一列分别进行返还,但如果这个时候我选择处理这个粗体的4(所在行和列的最大值都是它),那么对于这个“十字”而言,它实际上只需要返还一次,因此我们可以在原先sum的基础上再加上heng-1或zong-1。
只是这样简单地处理每行和每列就可以了吗?并不是,因为这样的行与列的组合非常多,所以这就涉及到了相关行与相关列匹配的问题,那么为了找到最大匹配,我们就需要将行最大和列最大相同的元组进行建边,然后跑一边二分图(如果不会二分图的话就去看看博客和模板吧 这里不想赘述了orz)
这部分很模板,我也是抄的板子,感觉二分图这里实在没什么好讲的orz
#include<stdio.h>
bool used[1005];
int heng[105],zong[105],he[105][105],cnt=0;
long long sum;
/*二分图*/
int num,to[70005],next[70005],head[1005],match[1005];
void makeline(int u,int v)
{
to[++cnt]=v;
next[cnt]=head[u];
head[u]=cnt;
}
bool mat(int u)
{
for(int edge=head[u];edge;edge=next[edge])
if(used[to[edge]]==false)
{
int k,v=to[edge];
k=match[v];used[v]=1;match[v]=u;
if(!k||mat(k))return true;
match[v]=k;
}
return false;
}
int main()
{
int m,n;
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)scanf("%d",&he[i][j]);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(heng[i]<he[i][j])heng[i]=he[i][j];
if(zong[j]<he[i][j])zong[j]=he[i][j];
/*贪心*/
if(he[i][j]>0)sum+=he[i][j]-1;
}
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
if(heng[i]==zong[j]&&he[i][j]!=0)makeline(i,m+j);
}
/*返还投影*/
for(int i=1;i<=m;i++)if(heng[i])sum=sum-heng[i]+1;
for(int j=1;j<=n;j++)if(zong[j])sum=sum-zong[j]+1;
/*特殊情况*/
for(int i=1;i<=m;i++)
{
for(int j=0;j<205;j++)used[j]=false;
if(mat(i))sum+=heng[i]-1;
}
printf("%lld\n",sum);
}