Luogu P6931 [ICPC 2017 WF] Mission Improbable 题解 [ 紫 ] [ 二分图最大匹配 ] [ 网络流 ]

Mission Improbable:还不错的二分图建模题。

和士兵占领那题挺相似的。

简要题意

给你一个立体图形的三视图,你需要求出总方块数减去摆出这个立体图形的最少方块数的值。

转化

首先考虑最简单的俯视图,只要当前这个位置不为 \(0\) 就必须强制放一个方块。因此我们可以把非 \(0\) 的方块高度全部减 \(1\) 来先处理掉这个情况。

接下来考虑如何处理左视图和主视图。观察后不难发现,这两个视角给答案的限制就是要求每一行或者每一列都有一个方格的高度达到某个值,其他的方格都可以不要。

我们先采取最劣的贪心策略,每一行每一列都先选不同的方格以满足答案的要求,后面再来调整,答案就是每行每列高度之和。

那么这样的贪心能不能进一步对方案优化呢?显然,当存在某一行和某一列所需高度相等,且该行该列相交的格子原高度不为 \(0\),我们可以将这一行和这一列所选的方格并成一个方格,这样代价就更小了。

同时不难发现,每一行最多与一列相匹配,每一列同理,不然一定是不优的。这就符合了二分图的性质,左部点就是代表行的点,右部点就是代表列的点,原高度不为 \(0\) 的方格就充当二分图上的边,跑二分图匹配就是尽可能将行列所用的方格合并到一块,以减少花费。

跑二分图最大匹配即可。注意这里可以不跑最大权匹配,因为不同高度的行和列根本不会在一个连通块中,所以相当于只要每个连通块内做权值都相同的普通二分图最大匹配即可。

跑完最大匹配,让之前选的方块数减掉最大匹配数就是最小需要使用的方块数。

我使用了网络流来实现,时间复杂度为 EK 或 Dinic 的复杂度,或者写匈牙利也是可以的。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int K=105,N=1005,M=100005;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,m,hx[K],hy[K],a[K][K],s,t;
ll smans=0,sma=0;
int h[N],cur[N],idx=1;
struct Edge{
	int v,ne;
	ll c;
}e[M];
void add(int u,int v,ll c)
{
	e[++idx]={v,h[u],c};
	h[u]=idx;
}
void addeg(int u,int v,ll c)
{
	add(u,v,c);
	add(v,u,0);
}
int getid(int id,int tp)
{
	return (id+tp*n);
}
ll d[N];
bool BFS()
{
	memset(d,0,sizeof(d));
	d[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=h[u];i;i=e[i].ne)
		{
			int v=e[i].v;ll c=e[i].c;
			if(d[v]==0&&c)
			{
				d[v]=d[u]+1;
				q.push(v);
				if(v==t)return 1;
			}
		}
	}
	return 0;	
}
ll dfs(int u,ll mf)
{
	if(u==t)return mf;
	ll sm=0;
	for(int i=cur[u];i;i=e[i].ne)
	{
		int v=e[i].v;ll c=e[i].c;
		cur[u]=i;
		if(d[v]==d[u]+1&&c)
		{
			ll res=dfs(v,min(c,mf));
			e[i].c-=res;
			e[i^1].c+=res;
			mf-=res;
			sm+=res;
			if(mf==0)break;
		}
	}
	if(sm==0)d[u]=0;
	return sm;
}
ll dinic()
{
	ll flow=0;
	while(BFS())
	{
		memcpy(cur,h,sizeof(h));
		flow+=dfs(s,inf);
	}
	return flow;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	s=n+m+1,t=n+m+2;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>a[i][j];
			hx[i]=max(hx[i],a[i][j]);
			hy[j]=max(hy[j],a[i][j]);
			smans+=(a[i][j]>0);sma+=a[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(a[i][j]>0&&hx[i]==hy[j])addeg(getid(i,0),getid(j,1),1);
		}
	}
	for(int i=1;i<=n;i++)addeg(s,getid(i,0),1);
	for(int i=1;i<=m;i++)addeg(getid(i,1),t,1);
	ll flow=dinic();
	for(int i=h[s];i;i=e[i].ne)
	{
		int v=e[i].v;ll c=e[i].c;
		if(c&&hx[v])smans+=hx[v]-1;
	}
	for(int i=1;i<=m;i++)if(hy[i])smans+=hy[i]-1;
	cout<<sma-smans;
	return 0;
}

总结

这题就是根据贪心,尽可能多选行和列共用的方格,然后其他行和列自己单独选一个格子就行,同时还运用到了一个二分图性质。还是很不错的一道题的。

posted @ 2025-04-13 14:47  KS_Fszha  阅读(29)  评论(0)    收藏  举报