题意简述
给你一个大小为 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
a−b≤c 或全都是
a
−
b
≥
c
a-b \geq c
a−b≥c 形式。因此我们应对其进行一些转化。
首先,从题目中得到的条件都是加法,因此为了将其转化为减法,可以将 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 −yj−xi≤−1;对于 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 −yj−xi≤0。在实际程序中,应将 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=1∑2n∣Δ−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=1∑2n∣Δ−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;
}