1. 程式人生 > >2016年ACM/ICPC北京賽區 C題(有源匯有上下界的最小費用最大流)

2016年ACM/ICPC北京賽區 C題(有源匯有上下界的最小費用最大流)

https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=5692

題意:給你一個n*n(n<=50)的棋盤,棋盤上每個點0表示白棋,1表示黑棋。

接下來n行,第i行給出棋盤的 第i行 的黑棋數目的理想範圍[x,y]。

接下來再n行,第i行給出棋盤的 第i列 的黑棋數目的理想範圍[x,y]。

接下來n*n/2行,每行四個數,x1,y1,x2,y2,表示位置(x1,y1)的棋子可以和位置(x2,y2)的棋子相互交換位置。

保證每個棋子只會輸入一次(可以且只可以交換一次),每兩個可交換的棋子都在同一行或者同一列。

求最少交換次數,使每行每列的黑棋數目都在理想範圍內。

如果無解,輸出-1。

思路:

比賽的時候建圖對了。。。可惜沒帶模板。。。最後一小時浪費了。。。(那這道題就是板子了)

其實還是很好想的,有源匯有上下界的最小費用最大流。

1、新建源點s,匯點t,統計黑棋總數目以及每一列、每一行黑棋的數目

2、源點s向每行、每列連邊,容量為那一行、那一列的黑棋數目,費用為0

3、每一行、每一列向匯點t建邊,容量上下界為理想數目上下界x、y,費用為0

4、對於同一行可交換的棋子,顏色相同忽略,顏色不同黑棋的那一行向白棋的那一行建邊,容量為1,費用為1。

5、對於同一列同上建邊。

然後套個有源匯有上下界的最小費用最大流板子就可以了。

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn=50+10;
int n;
int chs[maxn][maxn],rl[maxn],cl[maxn],rh[maxn],ch[maxn];
int r[maxn],c[maxn];
int s,t;
int sups,supt,digflow;
const int maxv=100+10;//最大頂點數
const int inf=2e9;//應大於費用總和
typedef pair<int,int> P;
struct Edge{int to,cap,rev,cost;}e;
vector<Edge>g[maxv];//圖的鄰接表示
int h[maxv],dist[maxv];//頂點的勢、最短距離,若cost為整數,則為int
int prevv[maxv],preve[maxv],V;//最短路中的前驅節點、對應的邊、頂點數
void addedge(int from,int to,int cap,int cost) // 加邊
{
    e.to=to;e.cap=cap;e.rev=g[to].size();e.cost=cost;
    g[from].push_back(e);
    e.to=from;e.cap=0;e.rev=g[from].size()-1;e.cost=-cost;
    g[to].push_back(e);
}
void addedge(int from,int to,int low,int up,int cost)
{
    digflow+=low;
    addedge(sups,to,low,cost);
    addedge(from,supt,low,cost);
    addedge(from,to,up-low,cost);
}
int mincostflow(int s,int t,int f)//求解s~t,流量為f的最小費用
{
    int res=0;
    memset(h,0,sizeof(4*t+4));
    while(f>0)
    {
        priority_queue<P, vector<P>, greater<P> > que;
        fill(dist,dist+V,inf);
        dist[s]=0;
        que.push(P(0,s));
        while(!que.empty())
        {
            P p=que.top();que.pop();
            int v=p.second;
            if(dist[v]<p.first) continue;
            for(int i=0;i<g[v].size();i++)
            {
                Edge &E = g[v][i];
                if(E.cap>0&&dist[E.to]>dist[v]+E.cost+h[v]-h[E.to])
                {
                    dist[E.to]=dist[v]+E.cost+h[v]-h[E.to];
                    prevv[E.to]=v;
                    preve[E.to]=i;
                    que.push(P(dist[E.to],E.to));
                }
            }
        }
        if(dist[t]==inf) return -1;
        for(int v=0;v<V;v++) h[v]+=dist[v];
        //沿s到t的最短路儘量增廣
        int d=f;
        for(int v=t;v!=s;v=prevv[v])
        d=min(d,g[prevv[v]][preve[v]].cap);
        f-=d;
        res+=h[t]*d;
        for(int v=t;v!=s;v=prevv[v])
        {
            Edge &E = g[prevv[v]][preve[v]];
            E.cap-=d;
            g[v][E.rev].cap+=d;
        }
    }
    return res;
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        digflow=0;
        s=2*n+1;sups=0;
        t=2*n+2;supt=t+1;
        memset(c,0,sizeof(c));
        memset(h,0,sizeof(h));
        for(int i=0;i<=supt;i++)
        g[i].clear();
        int mx=0;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&chs[i][j]);
            c[j]+=chs[i][j];
            h[i]+=chs[i][j];
            mx+=chs[i][j];
        }
        for(int i=1;i<=n;i++)
        {
            addedge(s,i,h[i],h[i],0);
            addedge(s,i+n,c[i],c[i],0);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&rl[i],&rh[i]);
            addedge(i,t,rl[i],rh[i],0);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&cl[i],&ch[i]);
            addedge(i+n,t,cl[i],ch[i],0);
        }
        for(int i=1,x1,x2,y1,y2;i<=(n*n)/2;i++)
        {
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            if(chs[x1][y1]==chs[x2][y2]) continue;
            if(chs[x1][y1]==0) swap(x1,x2),swap(y1,y2);
            if(x1==x2) addedge(n+y1,n+y2,1,1);
            else addedge(x1,x2,1,1);
        }

        addedge(t,s,mx,inf,0);
        V=2*n+4;
        int ans=mincostflow(sups,supt,digflow);
        printf("%d\n",ans);
    }
    return 0;
}