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


    题目

    题目大意

    题目化简一下,就变成:
    构造一个(01)数列(A),使得(D=sum A_iA_jB_{i,j}-sum A_iC_i)最大。
    问这个最大的(D)是多少。


    正解

    其实这是一个网络流的二元关系问题……
    如果(A_i)(1),则会有(-C_i)的贡献。
    如果(A_i)(A_j)皆为(1),则会有(B_{i,j})的贡献。
    然后很显然地,(70)分的方法就出来了:每个点朝汇点连一条容量为(C_i)的边,对于每个(B_{i,j}),建一个新点,从源点朝它连一条容量为(B_{i,j})的边,它朝(i)(j)连容量为无限大的边。然后最小割即可。
    这个算法的瓶颈在于这些新点太多了,能不能不用建立新点?
    实际上有个很妙的方法:对于每一对(i)(j),从原点向(i)连一条容量为(B_{i,j})的边,同样地向(j)连一条容量为(B_{j,i})的边。(i)(j)连一条容量为(B_{i,j})的边,(j)(i)连一条容量为(B_{j,i})的边。
    那么这有什么用呢?当(C_i)的那条边被保留的时候,源点向(i)连的那条(B_{i,j})的边会被割掉,还有源点连向(j)或者(j)连向(i)的那条边也会被割掉。

    另一种建图方式跟这个比较类似,只是把边权换成了(frac{B_{i,j}+B_{j,i}}{2})罢了。因为只要保留(C_i)或者(C_j),割掉的边都是(B_{i,j}+B_{j,i})
    对于源点向(i)(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
    ",all-flow());
    	return 0;
    }
    

    总结

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

  • 相关阅读:
    1
    iulg
    实验10
    作业5 指针应用
    作业4 函数应用
    实验9 指针
    实验 8 数组2
    实验7
    实验6 数组1
    实验5
  • 原文地址:https://www.cnblogs.com/jz-597/p/11423015.html
Copyright © 2020-2023  润新知