HDU-6532 Chessboard 2019广东省省赛B题(费用流)

比赛场上很容易想到是费用流,但是没有想到建图方法qwq,太弱了。

这里直接贴官方题解:

费用流。离散化坐标,每行用一个点表示,每列也用一个点表示。表示第i-1行的点向表示第i行的点连边,容量为第i行及以后能拿的棋子数的上限,费用为0,同理表示相邻列的点两两连边。若第i行第j列上有棋子,则表示第i行的点向表示第j列的点连边,容量为1,费用为该棋子的价值。可以定义源点表示第0行,汇点表示第0列,源点到汇点的最大费用流即为答案。

就是按照题解的建图方法,还有一些小细节:先要排序排除无用限制来减少限制边数,不然会超时。我用的办法是,按限制从小到大排序,大限制当且仅当它的行数小于小限制行数时才有用。列同理。这里想不明白的建议画图细细想。然后就是连边来表示限制条件:行的话就是(i-1)->i行连边,列的话就是i->(i-1)列连边,这是因为0行是源点0列是汇点所致的,行点要靠它的入边来限制流量,列点要靠出边来限制流量。

细节详见代码及注释:

#include<bits/stdc++.h>
using namespace std;
const int N=5000+10;
const int M=100000+10;
const int INF=0x3f3f3f3f;
int n,m,r,c,s,t,maxflow,mincost;
int nx,ny,x[N],y[N],xx[N],yy[N],bx[N],by[N];
struct edge{
    int nxt,to,cap,cost;
}edges[M<<1];
int cnt=1,head[N],pre[N];

struct dat{ int t,l; } R[M],C[M];
bool cmp(dat a,dat b) { return a.l<b.l || a.l==b.l && a.t<b.t; }

void add_edge(int x,int y,int z,int c) {
    edges[++cnt].nxt=head[x]; edges[cnt].to=y; edges[cnt].cap=z; edges[cnt].cost=c; head[x]=cnt;
}

queue<int> q;
int dis[N],lim[N]; 
bool inq[N];
bool spfa(int s,int t) {
    while (!q.empty()) q.pop();
    memset(dis,0x3f,sizeof(dis));
    memset(inq,0,sizeof(inq));
    dis[s]=0; inq[s]=1; lim[s]=INF; q.push(s);
    while (!q.empty()) {
        int x=q.front(); q.pop();
        for (int i=head[x];i;i=edges[i].nxt) {
            edge e=edges[i];
            if (e.cap && dis[x]+e.cost<dis[e.to]) {
                dis[e.to]=dis[x]+e.cost;
                pre[e.to]=i;  //即e.to这个点是从i这条边来的 
                lim[e.to]=min(lim[x],e.cap);
                if (!inq[e.to]) { q.push(e.to); inq[e.to]=1; }
            }
        }
        inq[x]=0;
    }
    return !(dis[t]==INF); 
}

void MCMF() {
    maxflow=0; mincost=0;
    while (spfa(s,t)) {
        int now=t;
        maxflow+=lim[t];
        mincost+=lim[t]*dis[t];
        while (now!=s) {
            edges[pre[now]].cap-=lim[t];
            edges[pre[now]^1].cap+=lim[t];
            now=edges[pre[now]^1].to;
        }
    }
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]),bx[i]=x[i],by[i]=y[i];
    nx=ny=n;
    scanf("%d",&m);
    char opt[3];
    for (int i=1;i<=m;i++) {
        int tx,ty; scanf("%s%d%d",opt,&tx,&ty);
        if (opt[0]=='R') R[++r]=(dat){tx,ty};
        if (opt[0]=='C') C[++c]=(dat){tx,ty};
    }
    int tmp=0; 
    sort(R+1,R+r+1,cmp);
    for (int i=1;i<=r;i++)  //排除行无用限制 
        if (tmp==0 || R[i].t<R[tmp].t) R[++tmp]=R[i],bx[++nx]=R[i].t;
    r=tmp; tmp=0;
    sort(C+1,C+c+1,cmp);
    for (int i=1;i<=c;i++)  //排除列无用限制 
        if (tmp==0 || C[i].t<C[tmp].t) C[++tmp]=C[i],by[++ny]=C[i].t;
    c=tmp;
    
    sort(bx+1,bx+nx+1); nx=unique(bx+1,bx+nx+1)-(bx+1);  //离散化 
    sort(by+1,by+ny+1); ny=unique(by+1,by+ny+1)-(by+1);  //离散化 
    
    for (int i=1;i<=n;i++) {
        int tx=lower_bound(bx+1,bx+nx+1,x[i])-bx;
        int ty=lower_bound(by+1,by+ny+1,y[i])-by;
        add_edge(tx,nx+1+ty,1,-i); add_edge(nx+1+ty,tx,0,i);  //棋子连边 
    }
    memset(xx,0x3f,sizeof(xx));
    memset(yy,0x3f,sizeof(yy));
    for (int i=1;i<=r;i++) {
        int tx=lower_bound(bx+1,bx+nx+1,R[i].t)-bx;
        xx[tx]=min(xx[tx],R[i].l);  //先求好限制条件,xx[i]代表i行及以后的最小限制 
    } 
    for (int i=1;i<=c;i++) {
        int ty=lower_bound(by+1,by+ny+1,C[i].t)-by;
        yy[ty]=min(yy[ty],C[i].l);  //列同行同理 
    }
    //这里是关键:行i-1->i为了限制i的出流,列i->i-1为了限制i的出流 
    for (int i=1;i<=nx;i++) add_edge(i-1,i,xx[i],0),add_edge(i,i-1,0,0);
    for (int i=1;i<=ny;i++) add_edge(nx+1+i,nx+1+i-1,yy[i],0),add_edge(nx+1+i-1,nx+1+i,0,0);
    
    s=0; t=nx+1;
    MCMF();
    cout<<-mincost<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/clno1/p/10957772.html