hiho1424 Asa's Chess Problem ( 上下界費(fèi)用流 )

題目暫無(wú)鏈接
( 北京2016區(qū)域賽C題 )

題目大意

給出一個(gè)N×N的01矩陣(N<=50,且N為偶數(shù))。有N*N/2對(duì)可交換格子,每個(gè)格子有且僅有一個(gè)可交換對(duì)象。并且,每對(duì)可交換的格子必然在同一行或同一列。每交換一次的代價(jià)是1。對(duì)于每一行和每一列,都有一個(gè)1的個(gè)數(shù)的上界和下界。
問(wèn),最少執(zhí)行多少次交換操作,可以滿足所有行和列的1的個(gè)數(shù)的要求,

解答

比較明顯的網(wǎng)絡(luò)流構(gòu)圖,需要用到上下界,注意構(gòu)圖點(diǎn)數(shù)不要太多,否則會(huì)TLE,該壓縮的必須要壓縮。

把每一行每一列都看成一個(gè)狀態(tài),由于可交換的格子必然在同一行或同一列,所以每次交換必然只是從一個(gè)狀態(tài)流向另一個(gè)狀態(tài)。所以,我們可以先預(yù)處理出開(kāi)始狀態(tài)每一行每一列有多少個(gè)1( 記為g[i] ),從源點(diǎn)S向狀態(tài)i (行或列)連一條上界下界均為g[i],費(fèi)用為0的邊。每個(gè)狀態(tài)i都向匯點(diǎn)T連一條上下界為讀入要求,費(fèi)用為0的邊。然后,統(tǒng)計(jì)出N*N/2對(duì)交換格子交換后由狀態(tài)i 到狀態(tài) j的個(gè)數(shù)( 先用二維數(shù)組壓縮統(tǒng)計(jì),記為swg[i][ j ] ),狀態(tài)i向狀態(tài)j連一條下界為0,上界為swg[i][ j ],費(fèi)用為1的邊。

根據(jù)上述的構(gòu)圖,新添超級(jí)源點(diǎn)SS和超級(jí)匯點(diǎn)ST,根據(jù)模型,轉(zhuǎn)化為下界均為0的網(wǎng)絡(luò)流。最后,直接求解費(fèi)用流即可。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <bitset>

#define bll long long
#define dou double
#define For(i,a,b) for (int i=(a),_##i=(b); i<=_##i; i++)
#define Rof(i,a,b) for (int i=(a),_##i=(b); i>=_##i; i--)
#define rep(i,a,b) for (int i=(a),_##i=(b); i<=_##i; i++)
#define rek(i,a,b) for (int i=(a),_##i=(b); i>=_##i; i--)
#define Mem(a,b) memset(a,b,sizeof(a))
#define Cpy(a,b) memcpy(a,b,sizeof(b))
//__builtin_popcountll

using std::sort;
using std::max;
using std::min;
using std::abs;
using std::swap;

// 費(fèi)用流模板,封裝在namespace里,避免變量,函數(shù)沖突
namespace fyl
{
    const int maxnode=100+100,maxedge=100*100+100*4+10;
    const int oo=1<<30;
    struct Edge
    {
        int node,next,flow,cost;
        Edge(int a=0,int b=0,int c=0,int d=0)
        {
            node=a,next=b,flow=c,cost=d;
        }
    }E[maxedge*2];
    int cnt;                    // 邊數(shù)統(tǒng)計(jì)
    int head[maxnode],path[maxnode],pre[maxnode],F[maxnode];
    int o[200000];              // spfa隊(duì)列,開(kāi)的較大,如果不夠可改成循環(huán)隊(duì)列
    bool vis[maxnode];          // spfa判斷是否點(diǎn)在隊(duì)列里

    void init()                 // 初始化,多組數(shù)據(jù)必須調(diào)用
    {
        cnt=0;
        memset(head,255,sizeof head);
    }
    void add_edge(int x,int y,int f,int c)  // 建邊,使用頻率很高,可以考慮using
    {
        E[cnt]=Edge(y,head[x],f,c),head[x]=cnt++;
        E[cnt]=Edge(x,head[y],0,-c),head[y]=cnt++;
    }
    bool spfa(int S,int T,int N)            // 找一條從S到T的最小費(fèi)用路徑,N是網(wǎng)絡(luò)流結(jié)點(diǎn)最后一個(gè)編號(hào)
    {
        for (int i=0; i<=N; i++) F[i]=oo;
        int h=0,t=0;
        o[t]=S; F[S]=0; vis[S]=1;
        for (; h<=t; h++)
        {
            int x=o[h];
            for (int i=head[x]; i!=-1; i=E[i].next)
            {
                int y=E[i].node;
                if (E[i].flow>0 && F[x]+E[i].cost<F[y])
                {
                    F[y]=F[x]+E[i].cost;
                    pre[y]=x;
                    path[y]=i;
                    if (!vis[y]) vis[o[++t]=y]=1;
                }
            }
            vis[x]=0;
        }
        return F[T]!=oo;
    }
    void get_ans(int S,int T,int &flow,int &cost)   // 找到路徑后,得到流量和費(fèi)用
    {
        int minf=oo;
        for (int x=T; x!=S; x=pre[x])
            minf=min(minf,E[path[x]].flow);
        for (int x=T; x!=S; x=pre[x])
        {
            E[path[x]].flow-=minf;
            E[path[x]^1].flow+=minf;
            cost+=E[path[x]].cost*minf;
        }
        flow+=minf;
    }
    void solve(int S,int T,int N,int &flow,int &cost)   // 建圖后求最小費(fèi)用最大流
    {
        while(spfa(S,T,N))
            get_ans(S,T,flow,cost);
    }
}

const int maxn=100+10;
int N;
int A[maxn][maxn],u[maxn],d[maxn];
int g[maxn],swg[maxn][maxn];

int Done()
{
    int S=0,T=2*N+1,num=2*N+1;  // 2*N個(gè)狀態(tài),表示行和列,num表示點(diǎn)的編號(hào)
    int SS=++num,ST=++num;
    int sumdown=0;              // 下屆的和
    fyl::init();                // 初始化
    Mem(g,0);                   // 統(tǒng)計(jì)初始矩陣每個(gè)狀態(tài)的1的個(gè)數(shù)
    Mem(swg,0);                 // 統(tǒng)計(jì)狀態(tài)間的交換次數(shù),用于壓縮邊
    For(i,1,N)
        For(j,1,N)
        {
            scanf("%d",&A[i][j]);
            g[i]+=A[i][j];
            g[j+N]+=A[i][j];
        }
    For(i,1,2*N) scanf("%d%d",&d[i],&u[i]); // 讀入行列上下界
    For(i,1,N*N/2)
    {
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        if (A[x1][y1]!=A[x2][y2])
        {
            if (A[x1][y1]==0) swap(x1,x2),swap(y1,y2);
            if (x1==x2) 
                swg[y1+N][y2+N]++;
            else
                swg[x1][x2]++;
        }
    }
    using fyl::add_edge;                // 頻繁出現(xiàn),先using
    For(i,1,2*N)
    {
        if (g[i])
        {
            add_edge(SS,i,g[i],0);
            add_edge(S,ST,g[i],0);
            sumdown+=g[i];
        }
        if (d[i]>0)
        {
            add_edge(SS,T,d[i],0);
            add_edge(i,ST,d[i],0);
            u[i]-=d[i];
            sumdown+=d[i];
        }
        if (u[i]) add_edge(i,T,u[i],0);
    }
    For(i,1,2*N)
        For(j,1,2*N)
            if (swg[i][j]) add_edge(i,j,swg[i][j],1);
    add_edge(T,S,fyl::oo,0);
    int flow=0,cost=0;
    fyl::solve(SS,ST,num,flow,cost);
    if (flow!=sumdown) return -1;   // 最大流要等于下屆可,否則無(wú)解
    return cost;                    // 返回有解的情況下的最小費(fèi)用
}


int main(int argc, char* argv[])
{
    for (; scanf("%d",&N)!=EOF; )
    {
        int ans=Done();
        printf("%d\n",ans);
    }
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容