[JZOJ1900] 【2010集训队出题】矩阵

题目

题目大意

题目化简一下,就变成:
构造一个 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,jAiCi最大。
问这个最大的 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;
}

总结

见到二元关系类型的题目,首先要想到网络流啊……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值