题目
题目大意
题目化简一下,就变成:
构造一个
01
01
01数列
A
A
A,使得
D
=
∑
A
i
A
j
B
i
,
j
−
∑
A
i
C
i
D=\sum A_iA_jB_{i,j}-\sum A_iC_i
D=∑AiAjBi,j−∑AiCi最大。
问这个最大的
D
D
D是多少。
正解
其实这是一个网络流的二元关系问题……
如果
A
i
A_i
Ai为
1
1
1,则会有
−
C
i
-C_i
−Ci的贡献。
如果
A
i
A_i
Ai和
A
j
A_j
Aj皆为
1
1
1,则会有
B
i
,
j
B_{i,j}
Bi,j的贡献。
然后很显然地,
70
70
70分的方法就出来了:每个点朝汇点连一条容量为
C
i
C_i
Ci的边,对于每个
B
i
,
j
B_{i,j}
Bi,j,建一个新点,从源点朝它连一条容量为
B
i
,
j
B_{i,j}
Bi,j的边,它朝
i
i
i和
j
j
j连容量为无限大的边。然后最小割即可。
这个算法的瓶颈在于这些新点太多了,能不能不用建立新点?
实际上有个很妙的方法:对于每一对
i
i
i和
j
j
j,从原点向
i
i
i连一条容量为
B
i
,
j
B_{i,j}
Bi,j的边,同样地向
j
j
j连一条容量为
B
j
,
i
B_{j,i}
Bj,i的边。
i
i
i向
j
j
j连一条容量为
B
i
,
j
B_{i,j}
Bi,j的边,
j
j
j向
i
i
i连一条容量为
B
j
,
i
B_{j,i}
Bj,i的边。
那么这有什么用呢?当
C
i
C_i
Ci的那条边被保留的时候,源点向
i
i
i连的那条
B
i
,
j
B_{i,j}
Bi,j的边会被割掉,还有源点连向
j
j
j或者
j
j
j连向
i
i
i的那条边也会被割掉。
另一种建图方式跟这个比较类似,只是把边权换成了
B
i
,
j
+
B
j
,
i
2
\frac{B_{i,j}+B_{j,i}}{2}
2Bi,j+Bj,i罢了。因为只要保留
C
i
C_i
Ci或者
C
j
C_j
Cj,割掉的边都是
B
i
,
j
+
B
j
,
i
B_{i,j}+B_{j,i}
Bi,j+Bj,i。
对于源点向
i
i
i和
j
j
j连的边,显然可以合并起来。所以图中的点和边的数量就大大减少了。
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#define N 610
inline int input(){
char ch=getchar();
while (ch<'0' || '9'<ch)
ch=getchar();
int x=0;
do{
x=x*10+ch-'0';
ch=getchar();
}
while ('0'<=ch && ch<='9');
return x;
}
int n;
int b[N][N],c[N];
struct EDGE{
int to,c;
EDGE *las;
} e[2000000];
int ne;
EDGE *last[N];
inline void link(int u,int v,int c){
e[ne]={v,c,last[u]};
last[u]=e+ne++;
}
int S,T;
#define rev(ei) (e+(((ei)-e)^1))
int dis[N],gap[N],BZ;
EDGE *cur[N];
int dfs(int x,int s){
if (x==T)
return s;
int have=0;
for (EDGE *ei=cur[x];ei;ei=ei->las){
cur[x]=ei;
if (ei->c && dis[x]==dis[ei->to]+1){
int t=dfs(ei->to,min(s-have,ei->c));
ei->c-=t,rev(ei)->c+=t,have+=t;
if (have==s)
return s;
}
}
cur[x]=last[x];
if (!--gap[dis[x]])
BZ=0;
dis[x]++;
gap[dis[x]]++;
return have;
}
inline int flow(){
gap[0]=n+2;
int res=0;
BZ=1;
while (BZ)
res+=dfs(S,INT_MAX);
return res;
}
int main(){
n=input();
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j)
b[i][j]=input();
for (int i=1;i<=n;++i)
c[i]=input();
S=n+1,T=n+2;
int all=0;
for (int i=1;i<=n;++i){
int sum=0;
for (int j=1;j<=n;++j)
sum+=b[j][i];
all+=sum;
link(S,i,sum),link(i,S,0);
for (int j=1;j<i;++j)
link(i,j,b[j][i]),link(j,i,b[i][j]);
link(i,T,c[i]),link(T,i,0);
}
printf("%d\n",all-flow());
return 0;
}
总结
见到二元关系类型的题目,首先要想到网络流啊……