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

时间: 2024-10-14 07:04:48

HDU-6532 Chessboard 2019广东省省赛B题(费用流)的相关文章

ACM-ICPC 2019南昌网络赛I题 Yukino With Subinterval

ACM-ICPC 2019南昌网络赛I题 Yukino With Subinterval 题目大意:给一个长度为n,值域为[1, n]的序列{a},要求支持m次操作: 单点修改 1 pos val 询问子区间中某个值域的数的个数,连续的相同数字只记为一个.(即统计数字段的个数) 2 L R x y 数据范围: 1 ≤ n,m ≤ 2×10^5 1 ≤ a[i] ≤ n 解题思路: 连续重复的数字只记一次.所以考虑将每个数字段除第一个出现外的数字都删去(记为0).在读入操作的时候暴力模拟,同时维护

HDU 5003 Osu!(鞍山网络赛G题)

HDU 5003 Osu! 题目链接 就一签到题,排序之后for一遍计算出答案即可 代码: #include <cstdio> #include <cstring> #include <iostream> #include <string> #include <vector> #include <set> #include <map> #include <algorithm> #include <cmat

TI MSP430工程配置及2019年电赛A题编程示例(使用430 F5529)

配置 第一步:右击工程,选择Options 第二步:在General Options的Target选项卡里选择对应的器件Device,这里是MSP430G2231 第三步:在Debugger里选择FET Debugger: 第四步: 检查FET Debugger里的设置是否正确,这里是Texas Instrument USB-IF采用Automatic方式 这里选择Automatic与Automatic selection,当用串口会自动连接上串口. 第五步:编译程序,下载验证 电赛A题源码 小

hdu6611 2019 多校3K 原始对偶费用流(正权化Dijkstra找增广路)

http://acm.hdu.edu.cn/showproblem.php?pid=6611 题很简单,一眼拆点费用流 就是点边拉满之后复杂度有点恐怖,比赛的时候没敢莽费用流 但是最后居然真的是费用流,不过必须上原始对偶且用Dijkstra增广 具体细节很多,大概就是指,原本的Dijktra无法处理负权图,我们就去想办法对所有的费用进行统一扩大,变成正权最短路. #include<bits/stdc++.h> #define fi first #define se second #define

HDU 5006 Resistance(鞍山网络赛J题)

HDU 5006 Resistance 思路:这题由于数据是随机的..电阻不是1就是0,就可以先缩点,把电阻为0的那些边缩掉,只考虑有电阻的边,这样的话缩下来点数就不多了,就可以利用高斯消元+基尔霍夫定律去搞了 代码: #include <cstdio> #include <cstring> #include <cmath> #include <vector> #include <algorithm> using namespace std; c

HDU 5001 Walk(鞍山网络赛E题)

HDU 5001 Walk 题目链接 思路:枚举每个要经过的点,然后进行状态转移,状态为dp[i][j],状态表示当前在j的点,已经走了i步,每次转移的时候,不从这个枚举的点出发,这样就可以求出所有路径经过该点的概率p, 然后1 - p就是不经过的答案 代码: #include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; con

HDU 5000 Clone(鞍山网络赛D题)

HDU 5000 Clone 这场就出了3题..就坑在这题上了,还好保住了名额 思路:要推出最大值的时候,每个人的属性和必然相同,并且这个和必然是所有和 / 2,这样的话,问题转化为给n个数字,要组合成sum / 2有多少种方法,就用dp背包推一遍就可以得解了. 现场的时候就没推出sum / 2就是答案 代码: #include <cstdio> #include <cstring> const int MOD = 1000000007; const int N = 2005; i

HDU 5011 Game(西安网络赛E题)

HDU 5011 Game 题目链接 思路:其实就求一个Nim和即可,要推也不难推,和为0下一个必然是胜态,因为至少取走一个,在怎么分也达不到原来那个值了,如果是非0值,就和原来Nim一样必然可以取一堆使得变成0 代码: #include <cstdio> #include <cstring> const int N = 100005; int n; long long a, sum; int main() { while (~scanf("%d", &

HDU 5073 Galaxy (2014鞍山现场赛D题)

题目链接:HDU 5073 Galaxy 题意:在一维的坐标系里,给出N个点坐标,转动K个点,使转动后这个星系的的惯性最小(根据题意惯性最小也就是 求所有星星到星系中心的距离最小,这个可以理解成方差最小).求最小的惯性. 思路: 先对序列排序,再求出算N-K个点惯性的递推式. 以三个为例: 预处理是 平均数和各项的平方和, 注意:n==k的特判 AC代码: #include <stdio.h> #include <algorithm> using namespace std; do