zoukankan      html  css  js  c++  java
  • 【BZOJ2127】happiness

     

    Time Limit: 1000 ms   Memory Limit: 256 MB

    Description

      高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大。

    Input

      第一行两个正整数n,m。接下来是六个矩阵第一个矩阵为n行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学选择文科获得的喜悦值。第二个矩阵为n行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学选择理科获得的喜悦值。第三个矩阵为n-1行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i+1行第j列的同学同时选择文科获得的额外喜悦值。第四个矩阵为n-1行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i+1行第j列的同学同时选择理科获得的额外喜悦值。第五个矩阵为n行m-1列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i行第j+1列的同学同时选择文科获得的额外喜悦值。第六个矩阵为n行m-1列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i行第j+1列的同学同时选择理科获得的额外喜悦值。

    Output

      输出一个整数,表示喜悦值总和的最大值

    Sample Input

      1 2
      1 1
      100 110
      1
      1000

    Sample Output

      1210
      【样例说明】
         两人都选理,则获得100+110+1000的喜悦值。
      【数据规模】
               对于100%以内的数据,n,m<=100 所有喜悦值均为小于等于5000的非负整数
     

    Solution

      这种相邻格子的问题,一般都是考虑两个相邻的格子的最小割模型,然后把所有模型叠加起来。

      现在考虑相邻的两人$x$和$y$。$x$选文或理的收益是$x_0,x_1$;$y$选文或理的收益是$y_0,y_1$;都选文的收益是$s_0$,都选理的收益是$s_1$。

      那么从总收益$sum=x_0+x_1+y_0+y_1+s_0+s_1$中减去最小割就是最优解。

      考虑一下逻辑关系:

        (1)如果$x$和$y$都选文,那么需要割去$x_1,y_1,s_1$

        (2)如果$x$和$y$都选理,那么需要割去$x_0,y_0,s_0$

        (3)如果$x$文$y$理,那么需要割去$x_1,y_0,s_0,s_1$

        (4)如果$x$理$y$文,那么需要割去$x_0,y_1,s_0,s_1$

      发现一个人选一科,必定割掉另外一科的喜悦值。那么由源点$S$向$x$连$x_0$的边,向$y$连$y_0$的边;$x$向汇点$T$连$x_1$的边,向$y$连$y_1$的边。

      

       现在,(1)和(2)还差$s_0$和$s_1$未刻画。因为割掉两条边以后貌似图就彻底分开了,不好再加入新的边来体现,我们考虑将$s_0$附加在$x_0$与$y_0$上,将$s_1$附加在$x_1$与$y_1$上,即各分一半:

       

      (1)和(2)刻画完毕。但是(3)和(4)在其中不适用了。

      如果$x$选文,$y$选理,那么割去的边是右上和左下两条边,其权值之和是$x_1+frac{1}{2}s_1+y_0+frac{1}{2}s_0$,但是我们期望的是$x_1+y_0+s_0+s_1$,不对啊。

      怎么办?期望值和当前值一作差,得$frac{1}{2}s_0+frac{1}{2}s_1=frac{1}{2}(s_0+s_1)$。想办法把它实例化!当割去的是右上和左下两条边时,已经形成最小割,那么强行把这条边塞进去!由$y$向$x$连一条权值为$frac{1}{2}(s_0+s_1)$的调整边即可。

      $x$理$y$文同理。

      

      中间的两条调整边,仅在二者文理不同的时候起作用。现在这个模型,已经可以刻画(1)~(4)所有的情况了。

      对全图进行建模,跑出最小割,用总收益和减去最小割即可。

    Important:

      我们不能对于每两个格子都像如上模型一样连接$(S,x),(S,y),(x,T),(y,T)$,将总收益和看做每两个格子的$sum$之和,然后将模型并起来跑。

      为什么?因为这样我们会发现某一些$x_0$、$x_1$和$y_0$、$y_1$被多算了几次,这是极不好的,模型之间出现了交集。

      事实上对于一个点$x$,我们将所有$S$->$x$的边都合并起来:即权值应该为:$x_0$或$y_0$加上$frac{1}{2} sum s_0$。$x$到$T$的边同理。

      而中间的调整边照常即可。

      这样当$sum$为所有格子的收益和的时候,用$sum$减去最小割的答案是对的。

    Tips:

      可以将边权乘上2,跑出最小割后除以2,即可忽略小数。


    #include <cstdio>
    #include <queue>
    using namespace std;
    const int N=10010,INF=2147000000;
    int n,m,a[110][110],b[110][110],ax[110][110],ay[110][110],bx[110][110],by[110][110];
    int sum;
    int S,T,dis[N],cur[N],h[N],tot;
    queue<int> q;
    struct Edge{int v,next,f;}g[N*20];
    inline int id(int x,int y){return (x-1)*m+y;}
    inline int min(int x,int y){return x<y?x:y;}
    inline void addEdge(int u,int v,int f){
        g[++tot].v=v; g[tot].f=f; g[tot].next=h[u]; h[u]=tot;
        g[++tot].v=u; g[tot].f=0; g[tot].next=h[v]; h[v]=tot;
    }
    bool bfs(){
        while(!q.empty()) q.pop();
        q.push(S);
        for(int i=1;i<=T;i++) dis[i]=-1;
        dis[S]=0;
        while(!q.empty()){
            int u=q.front(); q.pop();
            for(int i=h[u],v;i;i=g[i].next)
                if(g[i].f&&dis[v=g[i].v]==-1){
                    dis[v]=dis[u]+1;
                    if(v==T) return true;
                    q.push(v);
                }
        }
        return dis[T]!=-1;
    }
    int dfs(int u,int delta){
        if(u==T) return delta;
        int ret=0,get;
        for(int i=cur[u],v;i&&delta;i=g[i].next)
            if(g[i].f&&dis[v=g[i].v]==dis[u]+1){
                get=dfs(v,min(delta,g[i].f));
                g[i].f-=get;
                g[i^1].f+=get;
                if(g[i].f) cur[u]=i;
                delta-=get;
                ret+=get;
            }
        if(!ret) dis[u]=-1;
        return ret;
    }
    int dinic(){
        int ret=0;
        while(bfs()){
            for(int i=1;i<=T;i++) cur[i]=h[i];
            ret+=dfs(S,INF);
        }
        return ret;
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]),sum+=a[i][j];
        for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&b[i][j]),sum+=b[i][j];
        for(int i=1;i<n;i++) for(int j=1;j<=m;j++) scanf("%d",&ax[i][j]),sum+=ax[i][j];
        for(int i=1;i<n;i++) for(int j=1;j<=m;j++) scanf("%d",&bx[i][j]),sum+=bx[i][j];
        for(int i=1;i<=n;i++) for(int j=1;j<m;j++) scanf("%d",&ay[i][j]),sum+=ay[i][j];
        for(int i=1;i<=n;i++) for(int j=1;j<m;j++) scanf("%d",&by[i][j]),sum+=by[i][j];
        S=n*m+1; T=n*m+2; tot=1;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                int u=id(i,j);
                addEdge(S,u,a[i][j]*2+ax[i-1][j]+ax[i][j]+ay[i][j-1]+ay[i][j]);
                addEdge(u,T,b[i][j]*2+bx[i-1][j]+bx[i][j]+by[i][j-1]+by[i][j]);
                if(i<n){
                    addEdge(u,id(i+1,j),ax[i][j]+bx[i][j]);
                    addEdge(id(i+1,j),u,ax[i][j]+bx[i][j]);
                }
                if(j<m){
                    addEdge(u,id(i,j+1),ay[i][j]+by[i][j]);
                    addEdge(id(i,j+1),u,ay[i][j]+by[i][j]);
                }
            }
        int get=dinic();
        get/=2;
        printf("%d
    ",sum-get);
        return 0;
    }
    奇妙代码
  • 相关阅读:
    机器学习sklearn(66):算法实例(二十三)SVM(一)概述
    机器学习sklearn(65):算法实例(二十二)聚类(五)KMeans (四) 总结
    机器学习sklearn(64):算法实例(二十一)聚类(四)KMeans (三) 案例:聚类算法用于降维,KMeans的矢量量化应用
    机器学习sklearn(63):算法实例(二十)聚类(三)KMeans (二) sklearn.cluster.KMeans
    机器学习sklearn(62):算法实例(十九)聚类(二)KMeans
    机器学习sklearn(61):算法实例(十八)聚类(一)概述
    机器学习sklearn(60):算法实例(十七)分类(十)逻辑回归(五)附录
    机器学习sklearn(59):算法实例(十六)分类(九)逻辑回归(四)实例 用逻辑回归制作评分卡
    机器学习sklearn(58):算法实例(十五)分类(八)逻辑回归(三)linear_model.LogisticRegression(二) 重要参数
    机器学习sklearn(57):算法实例(十四)分类(七)逻辑回归(二)linear_model.LogisticRegression(一) 重要参数
  • 原文地址:https://www.cnblogs.com/RogerDTZ/p/8018135.html
Copyright © 2011-2022 走看看