51Nod2629-偷箱子【贪心+二分图匹配】 难度:***

问题描述:

有一个箱子堆,里面有很多的箱子,为了防止有人偷,放置了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);  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值