【洛谷同步】UVA11671题解

题意简述

给你一个大小为 n × n n \times n n×n 的零矩阵,每次向整行或整列中的每个元素加一或减一,求使原矩阵满足与 0 相关的大小要求的最小次数。

题目分析

这个题是一个比较裸的差分约束,但是条件需要一些转化。首先很明显,题目中对矩阵操作没有先后之分,因此最终每个位置值的变化可以直接拆分为行与列分别加上减去的数之和。我们可以分别设两个数组 x x x y y y,其中 x i x_i xi y i y_i yi 分别表示第 i i i 行与第 i i i 列最终加上或减去的数。那么,题目中的约束矩阵的第 i i i 行与第 j j j 列的字符,如果是 0,那么代表 x i + y j = 0 x_i+y_j=0 xi+yj=0;如果是 +,那么代表 x i + y j > 0 x_i+y_j>0 xi+yj>0;如果是 -,那么代表 x i + y j < 0 x_i+y_j<0 xi+yj<0。看到这些不等式,我们很容易联想到差分约束。然而,差分约束要求不等式全都是 a − b ≤ c a-b\leq c abc 或全都是 a − b ≥ c a-b \geq c abc 形式。因此我们应对其进行一些转化。

首先,从题目中得到的条件都是加法,因此为了将其转化为减法,可以将 x x x y y y 全部存为其相反数,在此我选择将 y y y 存为相反数。接下来,对于 x i + y j > 0 x_i+y_j>0 xi+yj>0,可转化为 − y j − x i ≤ − 1 -y_j-x_i\leq-1 yjxi1;对于 x i + y j < 0 x_i+y_j<0 xi+yj<0,可转化为 x i − ( − y j ) ≤ − 1 x_i-(-y_j)\leq-1 xi(yj)1;对于 x i + y j = 0 x_i+y_j=0 xi+yj=0,可转化为 x i − ( − y j ) ≤ 0 x_i-(-y_j)\leq0 xi(yj)0 − y j − x i ≤ 0 -y_j-x_i\leq0 yjxi0。在实际程序中,应将 y y y x x x 合并为一个数组以适应差分约束的格式再进行差分约束的过程。

至此,我们求出了满足要求的一组解 z z z , 那么接下来探讨一下最优解如何求出。因为所有解都可以用差分约束求出的解减去一个 Δ \Delta Δ 来得到,也就是说我们需要求一个 Δ \Delta Δ 使得解最优,即 ∑ i = 1 2 n ∣ Δ − z i ∣ \sum \limits_{i=1}^{2n}|\Delta-z_i| i=12nΔzi 最小。很显然,根据数学初联学到的基础知识, Δ \Delta Δ 应取 z z z 的中位数。准确一点,因为 2 n 2n 2n 是偶数,所以 z n ≤ Δ ≤ z n + 1 z_n\leq\Delta\leq z_{n+1} znΔzn+1。证明其实很简单:当 z n ≤ Δ ≤ z n + 1 z_n\leq\Delta\leq z_{n+1} znΔzn+1 时,在 z z z 中刚好有 n n n 个数小于等于 Δ \Delta Δ n n n 个数大于等于 Δ \Delta Δ,因此 ∑ i = 1 2 n ∣ Δ − z i ∣ \sum \limits_{i=1}^{2n}|\Delta-z_i| i=12nΔzi 中的 Δ \Delta Δ 刚好被抵消掉,因此是最值,比如下图就是一个例子。

最后给出代码:

#include<bits/stdc++.h>
using namespace std;
int hd[210],n,c,d[210],v[210],s[210],cnt,sm;
char a[210];
queue<int>q,t;
struct nd
{
	int nt,v,w;
}e[40010];
void adde(int u,int v,int w)
{
	e[c].v=v;
	e[c].w=w;
	e[c].nt=hd[u];
	hd[u]=c++;
}
bool spfa() //差分约束中的最短路
{
	q=t;
	for(int i=1;i<=2*n;i++)
		q.push(i),d[i]=0,v[i]=1,s[i]=1;
	while(q.size())
	{
		int t=q.front();
		q.pop();
		for(int i=hd[t];i!=-1;i=e[i].nt)
		{
			int vt=e[i].v;
			if(d[vt]>d[t]+e[i].w)
			{
				d[vt]=d[t]+e[i].w;
				if(v[vt]==0)
				{
					q.push(vt);
					v[vt]=1;
					s[vt]++;
					if(s[vt]>2*n+1)
						return 0;//点入队次数过多,有负环
				}
			}
		}
		v[t]=0;
	}
	return 1;//无负环
}
int main()
{
	while(scanf("%d",&n),n!=-1)
	{
		cnt++;
		c=0;
		memset(hd,-1,sizeof hd);
		for(int i=1;i<=n;i++)
		{
			scanf("%s",a+1);
			for(int j=1;j<=n;j++)
			{
				//加边
				if(a[j]=='+')
					adde(j+n,i,-1);
				else if(a[j]=='-')
					adde(i,j+n,-1);
				else
	                adde(i,j+n,0),adde(j+n,i,0);
			}
		}
		printf("Case %d: ",cnt);
		if(spfa())
		{
			sm=0;
			sort(d+1,d+2*n+1);//排序以方便求中位数。
			for(int i=1;i<=2*n;i++)
				sm+=abs(d[i]-d[n]);//取中位数 d[n] 作为 Δ。
			printf("%d\n",sm);
		}
		else
			printf("-1\n");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值