[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\n",all-flow());
    return 0;
}

总结

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

猜你喜欢

转载自www.cnblogs.com/jz-597/p/11423015.html