网络流24题(23/24)

网络流24题

这几天就慢慢把网络流补一补吧,慢慢更新

1.餐巾计划问题

🔗

主要考虑建图

1.第一天其实就可以把所有需要买的餐巾全买了,\(S\rightarrow 1, cap = INF, fee = p\)

2.每天都需要消耗餐巾,控制流量,\(i\rightarrow T, cap = r_i, fee = 0\)

3.这一天剩下的没用的餐巾可以留到下一天,\(i\rightarrow i+1,cap = INF, fee = 0\)

4.这一天用过的可以清洗之后再用,由于这部分流量需要流到汇点,所以需要从源点连到一个新节点,然后从这个节点连出去,也就是\(S\rightarrow newnode,cap = r_i, fee = 0\)\(newnode\rightarrow i+m1, cap = INF, fee = f1\)\(newnode\rightarrow i+m2, cap = INF, fee = f2\)
这样建完图就可以跑费用流了

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 4444;
#define S 0
#define T MAXN - 1
using LL = int_fast64_t;
const LL INF = 0x3f3f3f3f3f3f3f3f;
struct EDGE{
    int to,fee,rev;
    LL cap;
    EDGE(){}
    EDGE(int _to, LL _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
int n,p,m1,f1,m2,f2,need[MAXN];
LL dist[MAXN],flow[MAXN];
int vis[MAXN],pre[MAXN],preid[MAXN];
void ADDEDGE(int u, int v, LL cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
LL mcmf(){
    LL cost = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
void build(){
    int nid = n;
    ADDEDGE(S,1,INF,p);
    for(int i = 1; i < n; i++) ADDEDGE(i,i+1,INF,0);
    for(int i = 1; i <= n; i++) ADDEDGE(i,T,need[i],0);
    for(int i = 1; i <= n; i++){
        ADDEDGE(S,++nid,need[i],0);
        if(i+m1<=n) ADDEDGE(nid,i+m1,INF,f1);
        if(i+m2<=n) ADDEDGE(nid,i+m2,INF,f2);
    }
}
int main(){
    scanf("%d",&n);
    for(int i = 1; i <= n; i++) scanf("%d",&need[i]);
    scanf("%d %d %d %d %d",&p,&m1,&f1,&m2,&f2);
    build();
    printf("%lld\n",mcmf());
    return 0;
}
2.星际转移问题

🔗

按时间分层的分层图最大流
每个飞船走的是一个循环,所以首先可以判断地球到月球是否有一条通路,可以用并查集来做
然后就是必然可以达到然后求最短时间的问题了,判断是否可行用最大流来判断,这个最短时间可以二分,也可以枚举,但是二分的化就必须每次重新建图,枚举的化可以再原图上建边跑Dinic,在残量网络上继续跑
现在要做的就是找出到\(t\)时刻最多能有多少人可以从地球到月球
这里我用的是枚举时间来做,在残量网络上继续跑,考虑如何建边:

1.我们可以把地球上的第一天作为源点,因为人都要从地球出发,月球看作汇点,所有人的终点就是月球,所以汇点可以不用按时间变化

2.由于人可以留在原地不动,只有时间变化,所以\(t\)时刻对于一个空间站(地球)来说,需要从上一个时刻往这个时刻连边,容量为无穷大

3.对于\(m\)艘太空船,要从\(t-1\)时刻的位置移动到\(t\)时刻的位置,所以就要从上个时刻的那个位置,向当前时刻的当前位置连边,容量为飞船容量(月球不用再往回连边)

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 6666;
const int INF = 0x3f3f3f3f;
#define S 1
#define T MAXN - 1
int n,m,k,w[22],root[22];
vector<int> vec[22];
struct EDGE{
    int to,cap,rev;
    EDGE(){};
    EDGE(int to, int cap, int rev):to(to),cap(cap),rev(rev){};
};
vector<EDGE> G[MAXN];
int iter[MAXN],rk[MAXN];
void ADDEDGE(int u, int v, int cap){
    G[u].push_back(EDGE(v,cap,(int)G[v].size()));
    G[v].push_back(EDGE(u,0,(int)G[u].size()-1));
}
bool bfs(){
    memset(rk,0,sizeof(rk));
    memset(iter,0,sizeof(iter));
    rk[S] = 1;
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(auto e : G[u]){
            if(!e.cap or rk[e.to]) continue;
            rk[e.to] = rk[u] + 1;
            que.push(e.to);
        }
    }
    return rk[T]!=0;
}
int dfs(int u, int flow){
    if(u==T) return flow;
    for(int &i = iter[u]; i < (int)G[u].size(); i++){
        auto &e = G[u][i];
        if(!e.cap or rk[e.to]!=rk[u]+1) continue;
        int d = dfs(e.to,min(e.cap,flow));
        if(d){
            e.cap -= d;
            G[e.to][e.rev].cap += d;
            return d;
        }
    }
    return 0;
}
void Dinic(int &flow){
    while(bfs()){
        int d = dfs(S,INF);
        while(d){
            flow += d;
            d = dfs(S,INF);
        }
    }
}
void build(int t){
    for(int i = 1; i <= n; i++) ADDEDGE((t-1)*n+i,t*n+i,INF);
    for(int i = 1; i <= m; i++){
        int sz = vec[i].size();
        int pre = vec[i][((t-1)%sz+sz)%sz], now = vec[i][t % sz];
        if(!pre) continue;
        ADDEDGE((t-1)*n+pre,now?t*n+now:T,w[i]);
    }
}
int findx(int x){ return x==root[x]?x:root[x]=findx(root[x]); }
void merge(int x, int y){
    if(findx(x)==findx(y)) return;
    root[findx(x)] = findx(y);
}
int main(){
    scanf("%d %d %d",&n,&m,&k); n+=1;
    for(int i = 0; i <= n; i++) root[i] = i;
    for(int i = 1; i <= m; i++){
        scanf("%d",&w[i]);
        int sz; scanf("%d",&sz);
        vec[i].resize(sz);
        for(int j = 0; j < sz; j++) scanf("%d",&vec[i][j]), vec[i][j]++;
        for(int j = 1; j < sz; j++) merge(vec[i][j],vec[i][j-1]);
    }
    if(findx(1)!=findx(0)){
        puts("0");
        return 0;
    }
    int flow = 0;
    for(int t = 1; ; t++){
        build(t);
        Dinic(flow);
        if(flow>=k){
            printf("%d\n",t);
            return 0;
        }
    }
    return 0;
}
3.飞行员配对方案问题

🔗
很标准的匈牙利算法的模板

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 222;
int match[MAXN],m,n,vis[MAXN],tot;
vector<int> G[MAXN];
bool dfs(int u){
    vis[u] = 1;
    for(int v : G[u]){
        if(match[v]==-1 or (!vis[match[v]] and dfs(match[v]))){
            match[v] = u;
            return true;
        }
    }
    return false;
}
void hungary(){
    memset(match,255,sizeof(match));
    for(int i = 1; i <= m; i++){
        memset(vis,0,sizeof(vis));
        if(dfs(i)) tot++;
    }
}
int main(){
    scanf("%d %d",&m,&n);
    int u, v;
    while(scanf("%d %d",&u,&v) and u+v+2) G[u].emplace_back(v);
    hungary();
    printf("%d\n",tot);
    for(int i = 1; i <= n; i++) if(match[i]!=-1) printf("%d %d\n",match[i],i);
    return 0;
}

当然也可以网络流做,从源点\(S\)向左部图的每个点建容量为\(1\)的边,右部图的每个点向汇点\(T\)建容量为\(1\)的边
然后对于存在匹配的就连边,容量大于等\(0\)就可以(多了也流不过去)
跑完最大流之后,最大匹配就是最大流
对于每个左部图的点,匹配的对应右部图的点就是满流的边对应的那个点,输出就好了

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 222;
const int INF = 0x3f3f3f3f;
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,rev;
    bool tag;
    EDGE(){};
    EDGE(int to, int cap, int rev, bool tag):to(to),cap(cap),rev(rev),tag(tag){};
};
vector<EDGE> G[MAXN];
int iter[MAXN],rk[MAXN],m,n;
void ADDEDGE(int u, int v, int cap){
    G[u].push_back(EDGE(v,cap,(int)G[v].size(),true));
    G[v].push_back(EDGE(u,0,(int)G[u].size()-1,false));
}
bool bfs(){
    memset(rk,0,sizeof(rk));
    memset(iter,0,sizeof(iter));
    rk[S] = 1;
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(auto e : G[u]){
            if(!e.cap or rk[e.to]) continue;
            rk[e.to] = rk[u] + 1;
            que.push(e.to);
        }
    }
    return rk[T]!=0;
}
int dfs(int u, int flow){
    if(u==T) return flow;
    for(int &i = iter[u]; i < (int)G[u].size(); i++){
        auto &e = G[u][i];
        if(!e.cap or rk[e.to]!=rk[u]+1) continue;
        int d = dfs(e.to,min(e.cap,flow));
        if(d){
            e.cap -= d;
            G[e.to][e.rev].cap += d;
            return d;
        }
    }
    return 0;
}
void Dinic(int &flow){
    while(bfs()){
        int d = dfs(S,INF);
        while(d){
            flow += d;
            d = dfs(S,INF);
        }
    }
}
int main(){
    scanf("%d %d",&m,&n);
    for(int i = 1; i <= m; i++) ADDEDGE(S,i,1);
    for(int i = m + 1; i <= n + m; i++) ADDEDGE(i,T,1);
    int u, v;
    while(scanf("%d %d",&u,&v) and u+v+2) ADDEDGE(u,v,1);
    int tot = 0; Dinic(tot);
    printf("%d\n",tot);
    for(int i = 1; i <= m; i++){
        for(auto e : G[i]){
            if(e.cap or !e.tag) continue;
            printf("%d %d\n",i,e.to);
            break;
        }
    }
    return 0;
}
4. 软件补丁问题

🔗
这难道不是状压之后最短路就能解决了吗,相当于\(2^{20}\)个点跑最短路,跑得飞快
勉强认为最短路是一种特殊的费用流吧,边的容量都为\(1\),然后跑一次费用流?

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = (1<<20);
const int INF = 0x3f3f3f3f;
int n,m,dist[MAXN],w[111];
char buf1[33],buf2[33];
vector<pair<int,int> > G;
pair<pair<int,int>,pair<int,int> > pr[111];
void getdest(int msk){
    G.clear();
    for(int i = 1; i <= m; i++){
        int in = pr[i].first.first, out = pr[i].first.second;
        int tozero = pr[i].second.first, toone = pr[i].second.second;
        if((msk&in)!=in or (msk&out)!=0) continue;
        G.push_back(make_pair(((msk&tozero)|toone),w[i]));
    }
}
int Dijkstra(int s, int t){
    priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> que;
    memset(dist,0x3f,sizeof(dist));
    dist[s] = 0;
    que.push(make_pair(dist[s],s));
    while(!que.empty()){
        auto p = que.top();
        que.pop();
        int u = p.second;
        if(u==t) break;
        if(dist[p.second]!=p.first) continue;
        getdest(u);
        for(auto e : G){
            int v = e.first, w = e.second;
            if(dist[u]+w<dist[v]){
                dist[v] = dist[u] + w;
                que.push(make_pair(dist[v],v));
            }
        }
    }
    if(dist[t]==INF) return 0;
    else return dist[t];
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i = 1; i <= m; i++){
        scanf("%d",&w[i]);
        scanf("%s %s",buf1,buf2);
        int in = 0, out = 0;
        int tozero = 0, toone = 0;
        for(int bit = 0; bit < n; bit++){
            if(buf1[bit]=='+') in |= (1<<bit);
            else if(buf1[bit]=='-') out |= (1<<bit);
            if(buf2[bit]=='-') tozero |= (1<<bit);
            else if(buf2[bit]=='+') toone |= (1<<bit);
        }
        tozero = ((1<<n)-1) - tozero;
        pr[i].first.first = in; pr[i].first.second = out;
        pr[i].second.first = tozero; pr[i].second.second = toone;
    }
    printf("%d\n",Dijkstra((1<<n)-1,0));
    return 0;
}
5. 太空飞行计划问题

🔗
最大权闭合子图
具体证明可以看这篇博客
我就说说怎么建图好了
源点向所有的正收益点建边,容量为收益,所有负收益点向汇点建边,容量为收益的绝对值,如果选\(A\)的前提是选\(B\),那么\(A\)\(B\)连边,容量无穷大
跑最小割
跑完之后图由最小割分割成两部分,一部分由源点\(S\)连接,另一部分由汇点\(T\)连接,其中连接\(S\)点的那部分就是最大权闭合图
最后要选的方案就是从\(S\)出发BFS到的点,就是\(S\)连接的集合

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 111;
const int INF = 0x3f3f3f3f;
int n,m,val[MAXN],w[MAXN];
vector<int> tools[MAXN];
char buf[10000];
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,rev;
    EDGE(){};
    EDGE(int to, int cap, int rev):to(to),cap(cap),rev(rev){};
};
vector<EDGE> G[MAXN];
int iter[MAXN],rk[MAXN];
void ADDEDGE(int u, int v, int cap){
    G[u].push_back(EDGE(v,cap,(int)G[v].size()));
    G[v].push_back(EDGE(u,0,(int)G[u].size()-1));
}
bool bfs(){
    memset(rk,0,sizeof(rk));
    memset(iter,0,sizeof(iter));
    rk[S] = 1;
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(auto e : G[u]){
            if(!e.cap or rk[e.to]) continue;
            rk[e.to] = rk[u] + 1;
            que.push(e.to);
        }
    }
    return rk[T]!=0;
}
int dfs(int u, int flow){
    if(u==T) return flow;
    for(int &i = iter[u]; i < (int)G[u].size(); i++){
        auto &e = G[u][i];
        if(!e.cap or rk[e.to]!=rk[u]+1) continue;
        int d = dfs(e.to,min(e.cap,flow));
        if(d){
            e.cap -= d;
            G[e.to][e.rev].cap += d;
            return d;
        }
    }
    return 0;
}
int Dinic(){
    int flow = 0;
    while(bfs()){
        int d = dfs(S,INF);
        while(d){
            flow += d;
            d = dfs(S,INF);
        }
    }
    return flow;
}
int tot = 0;
void build(){
    for(int i = 1; i <= n; i++) ADDEDGE(i,T,w[i]);
    for(int i = 1; i <= m; i++){
        for(int tool : tools[i]) ADDEDGE(i+n,tool,INF);
        ADDEDGE(S,i+n,val[i]); tot += val[i];
    }
}
int main(){
    cin >> m >> n;
    for(int i = 1; i <= m; i++){
        scanf("%d",&val[i]);
        memset(buf,0,sizeof(buf));
        cin.getline(buf,10000);
        int ulen=0,tool;
        while(sscanf(buf+ulen,"%d",&tool)==1){
            tools[i].push_back(tool);
            if (tool==0) ulen++;
            else while (tool) tool/=10, ulen++;
            ulen++;
        }
    }
    for(int i = 1; i <= n; i++) scanf("%d",&w[i]);
    build();
    int ret = tot - Dinic();
    for(int i = 1; i <= m; i++) if(rk[i+n]) printf("%d ",i); puts("");
    for(int i = 1; i <= n; i++) if(rk[i]) printf("%d ",i); puts("");
    printf("%d\n",ret);
    return 0;
}
6. 试题库问题

🔗

这个网络流比较好建图
首先源点向每个题目连容量为\(1\)的边,因为每道题目只能被用一次,然后各个类型向汇点连边,容量为需要该类型的题目数量,最后每道题向相应的类型连一条边,容量为\(1\),判断流量是否是是各个类型需要题目的总和
对于输出方案数,对于每道题目,如果连向对应的类型的那条边满流那就说明这道题目对应的类型就是这条边对应的

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 4e3+7;
const int INF = 0x3f3f3f3f;
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,rev;
    EDGE(){};
    EDGE(int to, int cap, int rev):to(to),cap(cap),rev(rev){};
};
vector<EDGE> G[MAXN];
int k,n,iter[MAXN],rk[MAXN];
void ADDEDGE(int u, int v, int cap){
    G[u].push_back(EDGE(v,cap,(int)G[v].size()));
    G[v].push_back(EDGE(u,0,(int)G[u].size()-1));
}
bool bfs(){
    memset(rk,0,sizeof(rk));
    memset(iter,0,sizeof(iter));
    rk[S] = 1;
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(auto e : G[u]){
            if(!e.cap or rk[e.to]) continue;
            rk[e.to] = rk[u] + 1;
            que.push(e.to);
        }
    }
    return rk[T]!=0;
}
int dfs(int u, int flow){
    if(u==T) return flow;
    for(int &i = iter[u]; i < (int)G[u].size(); i++){
        auto &e = G[u][i];
        if(!e.cap or rk[e.to]!=rk[u]+1) continue;
        int d = dfs(e.to,min(e.cap,flow));
        if(d){
            e.cap -= d;
            G[e.to][e.rev].cap += d;
            return d;
        }
    }
    return 0;
}
int Dinic(){
    int flow = 0;
    while(bfs()){
        int d = dfs(S,INF);
        while(d){
            flow += d;
            d = dfs(S,INF);
        }
    }
    return flow;
}
vector<int> vec[22];
int main(){
    int tot = 0;
    scanf("%d %d",&k,&n);
    for(int i = 1; i <= k; i++){
        int num; scanf("%d",&num);
        ADDEDGE(i+n,T,num);
        tot += num;
    }
    for(int i = 1; i <= n; i++){
        int num; scanf("%d",&num);
        ADDEDGE(S,i,1);
        while(num--){
            int x; scanf("%d",&x);
            ADDEDGE(i,x+n,1);
        }
    }
    tot -= Dinic();
    if(tot){
        puts("No Solution!");
        return 0;
    }
    for(int i = 1; i <= n; i++){
        for(auto e : G[i]) if(!e.cap and e.to>n){
            vec[e.to-n].push_back(i);
            break;
        }
    }
    for(int i = 1; i <= k; i++){
        printf("%d:",i);
        for(int x : vec[i]) printf(" %d",x);
        puts("");
    }
    return 0;
}
7.最小路径覆盖问题

🔗
要求的是最小不相交路径覆盖,先说结论,最小不相交路径覆盖数为顶点数减去拆点后的最大匹配数
考虑一开始每个点是一条路径,每有一对匹配,两条路径就能合并成一条路径,总路径数就\(-1\),所以得证
最大匹配还是直接跑匈牙利好了

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 333;
int n,m,vis[MAXN],match[MAXN],deg[MAXN];
vector<int> G[MAXN];
bool dfs(int u){
    vis[u] = 1;
    for(int v : G[u]){
        if(match[v]==-1 or (!vis[match[v]] and dfs(match[v]))){
            match[v] = u;
            return true;
        }
    }
    return false;
}
int hungary(){
    int tot = 0;
    memset(match,255,sizeof(match));
    for(int i = 1; i <= n; i++){
        memset(vis,0,sizeof(vis));
        if(dfs(i)) tot++;
    }
    return tot;
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i = 1; i <= m; i++){
        int u, v; scanf("%d %d",&u,&v);
        deg[v]++;
        G[u].push_back(v);
    }
    int tot = hungary();
    memset(vis,0,sizeof(vis));
    vector<int> vec;
    queue<int> que;
    for(int i = 1; i <= n; i++) if(!deg[i]) que.push(i);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vec.push_back(u);
        for(int v : G[u]) if(!--deg[v]) que.push(v);
    }
    reverse(vec.begin(),vec.end());
    for(int i = 0; i < n; i++){
        if(vis[vec[i]]) continue;
        int u = vec[i];
        stack<int> stk;
        while(u!=-1){
            vis[u] = true;
            stk.push(u);
            u = match[u];
        }
        while(!stk.empty()) printf("%d ",stk.top()), stk.pop();
        puts("");
    }
    printf("%d\n",n-tot);
    return 0;
}
8.魔术球问题

🔗
也是一个最小路径覆盖问题,首先要构造出图来,如果\(i\)\(j\)的和是平方数的话\(i\)就可以向\(j\)连一条边(\(i<j\)),然后就可以枚举+最小路径覆盖判断是否最小路径\(\le n\),枚举到最多能放的就好了

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2222;
int n,match[MAXN],vis[MAXN],deg[MAXN];
vector<int> G[MAXN];
bool sqt(int x){ for(int i = 1; i * i <= x; i++) if(i*i==x) return true; return false; }
bool dfs(int u, int tp){
    vis[u] = 1;
    for(int v : G[u]){
        if(v>tp) return false;
        if(match[v]==-1 or (!vis[match[v]] and dfs(match[v],tp))){
            match[v] = u;
            return true;
        }
    }
    return false;
}
int hungary(int num){
    int tot = 0;
    for(int i = 1; i <= num; i++) match[i] = -1;
    for(int i = 1; i <= num; i++){
        for(int j = 1; j <= num; j++) vis[j] = 0;
        if(dfs(i,num)) tot++;
    }
    return tot;
}
bool check(int m){ return m - hungary(m) <= n; }
int main(){
    scanf("%d",&n);
    for(int i = 1; i < MAXN; i++) for(int j = i + 1; j < MAXN; j++) if(sqt(i+j)) G[i].push_back(j);
    int ret = n;
    while(check(ret+1)) ret++;
    printf("%d\n",ret);
    memset(vis,0,sizeof(vis));
    for(int i = ret; i >= 1; i--){
        if(vis[i]) continue;
        stack<int> stk;
        int u = i;
        while(u!=-1){
            stk.push(u);
            vis[u] = true;
            u = match[u];
        }
        while(!stk.empty()) printf("%d ",stk.top()), stk.pop();
        puts("");
    }
    return 0;
}
9. 最长不下降子序列问题

🔗
第一问直接\(n^2\)的LIS跑出来\(s\)
对于第二问和第三问用网络流来做,先考虑第二问的建图:

1.每个点只能用一次,那么我们可以把每个点拆开来变成\(node_x,node_y\)然后\(node_x\)\(node_y\)连边,容量为\(1\)

2.由于起点必然是\(dp\)值是\(1\)的点,所以源点向每个\(dp\)值为\(1\)的点连边,容量为\(1\)

3.终点必然是\(dp\)值是\(s\)的点,那么\(dp\)值为\(s\)的点向汇点连边,容量为\(1\)

4.因为要用到中间的节点,所以对于每个点\(x\),再其后面的点\(y\),如果\(dp[y]==dp[x]+1\)并且\(y\ge x\),那么从\(node_y[x]\)\(node_x[y]\)连边,容量为\(1\)

这样建图跑出来的就是第二问的答案
对于第三问,把\(1\)号点和\(n\)号点拆开来的点之间的容量变成\(INF\),然后源点向\(1\)号点的边容量变为\(INF\),如果\(dp[n]==s\)的话,\(n\)号点向汇点的边容量也要变成\(INF\)
\(Dinic\)在原来的残流网络继续跑就行了
跑出来就是第三问答案了

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2333;
const int INF = 0x3f3f3f3f;
int n,A[MAXN],f[MAXN],s;
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,rev;
    EDGE(){};
    EDGE(int to, int cap, int rev):to(to),cap(cap),rev(rev){};
};
vector<EDGE> G[MAXN];
int iter[MAXN],rk[MAXN];
void ADDEDGE(int u, int v, int cap){
    G[u].push_back(EDGE(v,cap,(int)G[v].size()));
    G[v].push_back(EDGE(u,0,(int)G[u].size()-1));
}
bool bfs(){
    memset(rk,0,sizeof(rk));
    memset(iter,0,sizeof(iter));
    rk[S] = 1;
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(auto e : G[u]){
            if(!e.cap or rk[e.to]) continue;
            rk[e.to] = rk[u] + 1;
            que.push(e.to);
        }
    }
    return rk[T]!=0;
}
int dfs(int u, int flow){
    if(u==T) return flow;
    for(int &i = iter[u]; i < (int)G[u].size(); i++){
        auto &e = G[u][i];
        if(!e.cap or rk[e.to]!=rk[u]+1) continue;
        int d = dfs(e.to,min(e.cap,flow));
        if(d){
            e.cap -= d;
            G[e.to][e.rev].cap += d;
            return d;
        }
    }
    return 0;
}
void Dinic(int &flow){
    while(bfs()){
        int d = dfs(S,INF);
        while(d){
            flow += d;
            d = dfs(S,INF);
        }
    }
}
void build(){
    for(int i = 1; i <= n; i++) if(f[i]==1) ADDEDGE(S,i,1);
    for(int i = 1; i <= n; i++) if(f[i]==s) ADDEDGE(i+n,T,1);
    for(int i = 1; i <= n; i++) ADDEDGE(i,i+n,1);
    for(int i = 1; i <= n; i++) for(int j = i + 1; j <= n; j++) if(A[j]>=A[i] and f[j]==f[i]+1) ADDEDGE(i+n,j,1);
}
int main(){
    scanf("%d",&n);
    for(int i = 1; i <= n; i++) scanf("%d",&A[i]);
    for(int i = 1; i <= n; i++) for(int j = i - 1; j >= 0; j--) if(A[i]>=A[j]) f[i] = max(f[i],f[j]+1);
    s = *max_element(f+1,f+1+n);
    printf("%d\n",s);
    build();
    int flow = 0;
    Dinic(flow); printf("%d\n",flow);
    ADDEDGE(1,n+1,INF); ADDEDGE(n,n+n,INF);
    if(f[1]==1) ADDEDGE(S,1,INF);
    if(f[n]==s) ADDEDGE(n+n,T,INF);
    Dinic(flow); printf("%d\n",n==1?1:flow);
    return 0;
}
10. 航空路线问题

🔗
就是找从\(S\)\(T\)两条不相交路径,并且要求路径总长最长
考虑建图:

1.两条路径,那么其实起点和终点都是可以用两次的,其他点只能用一次,每个点拆成两个点,除了起点和终点两个点拆点后建边容量为\(2\),其余的点拆点后容量都是\(1\),费用都是\(-1\),因为要跑最小费用,所以费用取负,最后费用就是点数

2.原图可以看作是有向图,而且拓扑序已经给定,所以每次连边从拓扑序小的向拓扑序大的连一条边,容量\(>=2\)就好了,费用是\(0\)(主要考虑到如果起点和终点直接相连的话这条连边是可以走两次的)

然后跑费用流就好了

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 333;
const int INF = 0x3f3f3f3f;
int n,m;
string s[MAXN];
map<string,int> mp;
vector<int> vec;
#define S 1
#define T (n + n)
int dist[MAXN],vis[MAXN],flow[MAXN],pre[MAXN],preid[MAXN];
struct EDGE{
    int to,cap,fee,rev;
    EDGE(){}
    EDGE(int _to, int _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, int cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
int mcmf(){
    int cost = 0;
    int flw = 0;
    while(spfa()){
        int u = T;
        flw += flow[T];
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return flw == 2 ? cost : 1;
}
void dfs(int u){
    if(u==T) return;
    if(u<=n) vec.push_back(u);
    for(auto &e : G[u]){
        int v = e.to;
        if((G[e.to][e.rev].cap) and (v==u+n or (u>n and v>u-n))){
            e.cap++;
            G[e.to][e.rev].cap--;
            dfs(v);
            return;
        }
    }
}
int main(){
    ____();
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> s[i];
        mp.insert(make_pair(s[i],i));
    }
    for(int i = 1; i <= m; i++){
        string x,y;
        cin >> x >> y;
        int u = mp[x], v = mp[y];
        if(u>v) u^=v^=u^=v;
        ADDEDGE(u+n,v,INF,0);
    }
    for(int i = 2; i < n; i++) ADDEDGE(i,i+n,1,-1);
    ADDEDGE(1,1+n,1,-1); ADDEDGE(n,n+n,1,-1);
    ADDEDGE(1,1+n,1,-1); ADDEDGE(n,n+n,1,-1);
    int ret = -mcmf();
    if(ret<0){
        cout << "No Solution!" << endl;
        return 0;
    }
    cout << ret - 2 << endl;
    dfs(S);
    for(int x : vec) cout << s[x] << endl;
    vec.clear();
    dfs(S);
    for(int i = (int)vec.size() - 2; i >= 0; i--) cout << s[vec[i]] << endl;
    return 0;
}
11. 方格取数问题

🔗

最小割,考虑先把每个点拆成两个点\(nodex,nodey\)\(S\)向所有\(nodex\)连边,容量为点权,所有\(nodey\)\(T\)连边,容量为点权,由于每个点和其上下左右四个位置的点不能重复出现,所以每个点向其上下左右四个点连边,边权为\(INF\),然后跑最小割,最后答案就是总和减去最小割的一半,因为每两个点都连了两次边,所以最后计算出来的最小割是两倍

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 2e4+7;
const int INF = 0x3f3f3f3f;
int n,m,A[111][111];
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,rev;
    EDGE(){};
    EDGE(int to, int cap, int rev):to(to),cap(cap),rev(rev){};
};
vector<EDGE> G[MAXN];
int iter[MAXN],rk[MAXN];
void ADDEDGE(int u, int v, int cap){
    G[u].push_back(EDGE(v,cap,(int)G[v].size()));
    G[v].push_back(EDGE(u,0,(int)G[u].size()-1));
}
bool bfs(){
    memset(rk,0,sizeof(rk));
    memset(iter,0,sizeof(iter));
    rk[S] = 1;
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(auto e : G[u]){
            if(!e.cap or rk[e.to]) continue;
            rk[e.to] = rk[u] + 1;
            que.push(e.to);
        }
    }
    return rk[T]!=0;
}
int dfs(int u, int flow){
    if(u==T) return flow;
    for(int &i = iter[u]; i < (int)G[u].size(); i++){
        auto &e = G[u][i];
        if(!e.cap or rk[e.to]!=rk[u]+1) continue;
        int d = dfs(e.to,min(e.cap,flow));
        if(d){
            e.cap -= d;
            G[e.to][e.rev].cap += d;
            return d;
        }
    }
    return 0;
}
int Dinic(){
    int flow = 0;
    while(bfs()){
        int d = dfs(S,INF);
        while(d){
            flow += d;
            d = dfs(S,INF);
        }
    }
    return flow;
}
int main(){
    scanf("%d %d",&n,&m);
    int tot = 0;
    for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d",&A[i][j]), tot += A[i][j];
    for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++){
        ADDEDGE(S,(i-1)*m+j,A[i][j]);
        ADDEDGE((i-1)*m+j+n*m,T,A[i][j]);
        for(int x = i - 1; x <= i + 1; x++) for(int y = j - 1; y <= j + 1; y++){
            if(x<1 or x>n or y<1 or y>m or abs(x-i)+abs(y-j)!=1) continue;
            ADDEDGE((i-1)*m+j,(x-1)*m+y+n*m,INF);
        }
    }
    printf("%d\n",tot-Dinic()/2);
    return 0;
}
12. 机器人路径规划问题
13. 圆桌问题

🔗

这个建模应该比较简单:
1.源点\(S\)向每个单位建边,容量为这个单位的人数
2.每个餐桌向汇点\(T\)建边,容量为餐桌容量
3.每个单位向各个餐桌连边,容量为\(1\)

先判断流量是否等于各单位人数总和,等的话找到每个单位对应点的匹配边,就是有流量的边

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 444;
const int INF = 0x3f3f3f3f;
int n,m,r[MAXN],c[MAXN];    
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,rev;
    EDGE(){};
    EDGE(int to, int cap, int rev):to(to),cap(cap),rev(rev){};
};
vector<EDGE> G[MAXN];
int iter[MAXN],rk[MAXN];
void ADDEDGE(int u, int v, int cap){
    G[u].push_back(EDGE(v,cap,(int)G[v].size()));
    G[v].push_back(EDGE(u,0,(int)G[u].size()-1));
}
bool bfs(){
    memset(rk,0,sizeof(rk));
    memset(iter,0,sizeof(iter));
    rk[S] = 1;
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(auto e : G[u]){
            if(!e.cap or rk[e.to]) continue;
            rk[e.to] = rk[u] + 1;
            que.push(e.to);
        }
    }
    return rk[T]!=0;
}
int dfs(int u, int flow){
    if(u==T) return flow;
    for(int &i = iter[u]; i < (int)G[u].size(); i++){
        auto &e = G[u][i];
        if(!e.cap or rk[e.to]!=rk[u]+1) continue;
        int d = dfs(e.to,min(e.cap,flow));
        if(d){
            e.cap -= d;
            G[e.to][e.rev].cap += d;
            return d;
        }
    }
    return 0;
}
int Dinic(){
    int flow = 0;
    while(bfs()){
        int d = dfs(S,INF);
        while(d){
            flow += d;
            d = dfs(S,INF);
        }
    }
    return flow;
}
int main(){
    int tot = 0;
    scanf("%d %d",&n,&m);
    for(int i = 1; i <= n; i++){
        scanf("%d",&r[i]);
        tot += r[i];
        ADDEDGE(S,i,r[i]);
        for(int j = 1; j <= m; j++) ADDEDGE(i,j+n,1);
    }
    for(int i = 1; i <= m; i++){
        scanf("%d",&c[i]);    
        ADDEDGE(i+n,T,c[i]);
    }
    int flow = Dinic();
    if(flow!=tot){
        puts("0");
        return 0;
    }
    puts("1");
    for(int i = 1; i <= n; i++){
        for(auto e : G[i]){
            if(e.to==S or e.cap) continue;
            printf("%d ",e.to-n);
        }
        puts("");
    }
    return 0;
}
14. 骑士共存问题

🔗

和上面的方格取数问题差不多,但是这个用上面我的做法的话会T最后一个点
所以这里考虑奇偶二分建图,对于每个\(i,j\)如果\(i+j\)是奇数就放在左半图,为偶就放在右半图,然后源点向左半图的点连边,容量为\(1\),右半图的点向汇点连边,边权是\(1\),左半图的点向可以跳到的右半图的点连边,容量为\(INF\),然后建图求最小割\(W\),最终答案就是\(n\cdot n - W\)
这样建图能少一半的点和一半的边,并且保证了各部图内部不会有连边
源点和汇点的连边是为了让两部分的图的每个点都带上权值,如果删掉一个点就会有\(1\)的损失,同时图之间的连边设为\(inf\)保证了不会断中间的边

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1e5+7;
const int INF = 0x3f3f3f3f;
const int dir[8][2] = {{2,1},{1,2},{2,-1},{1,-2},{-1,2},{-2,1},{-1,-2},{-2,-1}};
int n,m,A[222][222];
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,rev;
    EDGE(){};
    EDGE(int to, int cap, int rev):to(to),cap(cap),rev(rev){};
};
vector<EDGE> G[MAXN];
int iter[MAXN],rk[MAXN];
void ADDEDGE(int u, int v, int cap){
    G[u].push_back(EDGE(v,cap,(int)G[v].size()));
    G[v].push_back(EDGE(u,0,(int)G[u].size()-1));
}
bool bfs(){
    memset(rk,0,sizeof(rk));
    memset(iter,0,sizeof(iter));
    rk[S] = 1;
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        for(auto e : G[u]){
            if(!e.cap or rk[e.to]) continue;
            rk[e.to] = rk[u] + 1;
            que.push(e.to);
        }
    }
    return rk[T]!=0;
}
int dfs(int u, int flow){
    if(u==T) return flow;
    for(int &i = iter[u]; i < (int)G[u].size(); i++){
        auto &e = G[u][i];
        if(!e.cap or rk[e.to]!=rk[u]+1) continue;
        int d = dfs(e.to,min(e.cap,flow));
        if(d){
            e.cap -= d;
            G[e.to][e.rev].cap += d;
            return d;
        }
    }
    return 0;
}
int Dinic(){
    int flow = 0;
    while(bfs()){
        int d = dfs(S,INF);
        while(d){
            flow += d;
            d = dfs(S,INF);
        }
    }
    return flow;
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i = 1; i <= m; i++){
        int x, y; scanf("%d %d",&x,&y);
        A[x][y] = 1;
    }
    for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++){
        if(A[i][j]) continue;
        if((i+j)&1) ADDEDGE(S,(i-1)*n+j,1);
        else{
            ADDEDGE((i-1)*n+j,T,1);
            continue;
        }
        for(int d = 0; d < 8; d++){
            int nx = i + dir[d][0];
            int ny = j + dir[d][1];
            if(nx<1 or nx>n or ny<1 or ny>n or A[nx][ny]) continue;
            if(((i+j)&1)^((nx+ny)&1)) ADDEDGE((i-1)*n+j,(nx-1)*n+ny,INF);
        }
    }
    printf("%d\n",n*n-m-Dinic());
    return 0;
}
15. 火星探险问题

🔗

拆点费用流
考虑建图:
1.每个点都要拆点变成\(nodex,nodey\),如果这个点是空地那么\(nodex\)\(nodey\)连边容量\(INF\),费用为\(0\)。如果这个点有石头,那么在之前的基础上再建一条边,容量为\(1\),费用为\(-1\),因为石头只能被取一次,费用取负是因为要跑最小费用

2.源点向\((1,1)\)点的\(nodex\)连边,容量为探测车的数量,费用为\(0\)

3.\((n,m)\)点的\(nodey\)向汇点连边,容量比探测车数量大就好了,因为之前源点处已经限制过流量了。费用为\(0\)

跑费用流
最后得到的最小费用取负就是取到的石头数量
考虑如何输出方案
可以处理\(num\)次,每次从源点\(DFS\),一直找到汇点,根据路径上的点来看是往南走还是往东走

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 4444;
const int INF = 0x3f3f3f3f;
const int dir[2][2] = {{1,0},{0,1}};
int A[44][44],num,n,m,dist[MAXN],flow[MAXN],pre[MAXN],preid[MAXN],vis[MAXN];

#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,fee,rev;
    EDGE(){}
    EDGE(int _to, int _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, int cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
int mcmf(){
    int cost = 0, flw = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        flw += flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
pair<int,int> decode(int tt){
    if(tt>n*m) tt -= n*m;
    return make_pair(tt / m - (tt%m==0?1:0) + 1,(tt-1) % m + 1); 
}
vector<pair<int,int> > vec;
bool check(int u, int v){
    auto pa = decode(u), pb = decode(v);
    return u+n*m==v or (pa.first==pb.first and pa.second+1==pb.second) or (pa.first+1==pb.first and pa.second==pb.second);
}
void dfs(int u){
    if(u==T) return;
    if(u>=1 and u<=n*m) vec.push_back(decode(u));
    for(auto &e : G[u]){
        if(!G[e.to][e.rev].cap) continue;
        if(u==S or e.to==T or check(u,e.to)){
            e.cap++;
            G[e.to][e.rev].cap--;
            dfs(e.to);
            return;
        }
    }
}
int main(){
    scanf("%d %d %d",&num,&m,&n);
    for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d",&A[i][j]);
    ADDEDGE(S,1,num,0);
    for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++){
        if(A[i][j]==1) continue;
        ADDEDGE((i-1)*m+j,(i-1)*m+j+n*m,INF,0);
        if(A[i][j]==2) ADDEDGE((i-1)*m+j,(i-1)*m+j+n*m,1,-1);
        for(int d = 0; d < 2; d++){
            int nx = i + dir[d][0];
            int ny = j + dir[d][1];
            if(nx<=n and ny<=m and A[nx][ny]!=1) ADDEDGE((i-1)*m+j+n*m,(nx-1)*m+ny,INF,0);
        }
    }
    ADDEDGE(n*m*2,T,num,0);//起点处已经限制流量了 这里流量只要大于等于num都可以
    mcmf();
    for(int i = 1; i <= num; i++){
        vec.clear();
        dfs(S);
        for(int p = 1; p < (int)vec.size(); p++){
            printf("%d ",i);
            if(vec[p].first==vec[p-1].first+1) puts("0");
            else puts("1");
        }
    }
    return 0;
}
16. 最长k可重线段集问题

🔗
可以先做下面那题,然后这题和那题基本一样,但是要处理一个问题就是线段的垂直于\(y\)轴的情况,这个情况下线段是只经过\(x_i\)这个点的,为了处理这种情况,我们可以把每个点拆成两个点,其中第一个点到第二个点之间这一段表示\(x_i\)这一个点的选择与否,第二个点到下一个位置的第一个点的意义是\(x_i\)\(x_{i+1}\)这段开区间的选择与否
所以建图的时候分两种情况,如果\(x_L==x_R\)的话,当前位置的第一个点向第二个点连边
否则左边位置的第二个点向右边位置的第一个点连边

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 3333;
using LL = int_fast64_t;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n,k,vis[MAXN],pre[MAXN],preid[MAXN];
LL dist[MAXN],w[MAXN],flow[MAXN];
pair<int,int> seg[MAXN];
vector<int> vec;
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,rev;
    LL fee,cap;
    EDGE(){}
    EDGE(int _to, LL _cap, LL _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, LL cap, LL fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
LL mcmf(){
    LL cost = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
int main(){
    scanf("%d %d",&n,&k);
    for(int i = 1; i <= n; i++){
        int y1, y2;
        scanf("%d %d %d %d",&seg[i].first,&y1,&seg[i].second,&y2);
        if(seg[i].first>seg[i].second) swap(seg[i].first,seg[i].second);
        w[i] = (LL)floor(sqrt(LL(seg[i].second-seg[i].first)*(seg[i].second-seg[i].first)+LL(y2-y1)*(y2-y1)));
        vec.push_back(seg[i].first); vec.push_back(seg[i].second);
    }
    sort(vec.begin(),vec.end());
    vec.erase(unique(vec.begin(),vec.end()),vec.end());
    for(int i = 1; i <= n; i++){
        seg[i].first = lower_bound(vec.begin(),vec.end(),seg[i].first) - vec.begin() + 1;
        seg[i].second = lower_bound(vec.begin(),vec.end(),seg[i].second) - vec.begin() + 1;
    }
    for(int i = 1; i <= (int)vec.size() * 2; i++) ADDEDGE(i-1,i,k,0);
    ADDEDGE((int)vec.size()*2,T,k,0);
    for(int i = 1; i <= n; i++){
        if(seg[i].first!=seg[i].second) ADDEDGE(seg[i].first*2,seg[i].second*2-1,1,-w[i]);
        else ADDEDGE(seg[i].first*2-1,seg[i].second*2,1,-w[i]);
    }
    printf("%lld\n",-mcmf());
    return 0;
}
17. 最长k可重区间集问题

🔗

长度换成其他权值一样可以做
显然对于每一条线段,计算完长度之后就可以把坐标离散化了
考虑建图:

1.每个坐标向下一个坐标连一条容量为\(k\),费用为\(0\)的边

2.对于每条线段,假设离散化之后坐标范围是\([L,R]\),那么就从\(L\)\(R\)连一条容量为\(1\),费用为\(-长度\)的边

这样建图之后可以发现,如果选择走一条线段,那么\([L,R]\)这段区间的可用流量就减少了\(1\),然后到\(R\)这个位置,这个用掉的流量又流回来了,可以供后一个线段使用

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1111;
const int INF = 0x3f3f3f3f;
int n,k,flow[MAXN],dist[MAXN],vis[MAXN],pre[MAXN],preid[MAXN],w[MAXN];
pair<int,int> seg[MAXN];
vector<int> vec;
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,fee,rev;
    EDGE(){}
    EDGE(int _to, int _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, int cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
int mcmf(){
    int cost = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
int main(){
    scanf("%d %d",&n,&k);
    for(int i = 1; i <= n; i++){
        scanf("%d %d",&seg[i].first,&seg[i].second);
        vec.push_back(seg[i].first); vec.push_back(seg[i].second);
        w[i] = seg[i].second - seg[i].first;
    }
    sort(vec.begin(),vec.end());
    vec.erase(unique(vec.begin(),vec.end()),vec.end());
    for(int i = 1; i <= n; i++){
        seg[i].first = lower_bound(vec.begin(),vec.end(),seg[i].first) - vec.begin() + 1;
        seg[i].second = lower_bound(vec.begin(),vec.end(),seg[i].second) - vec.begin() + 1;
    }
    for(int i = 1; i <= (int)vec.size(); i++) ADDEDGE(i-1,i,k,0);
    ADDEDGE((int)vec.size(),T,k,0);
    for(int i = 1; i <= n; i++) ADDEDGE(seg[i].first,seg[i].second,1,-w[i]);
    printf("%d\n",-mcmf());
    return 0;
}
18. 汽车加油行驶问题

🔗

建图跑分层图最短路

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 222;
const int INF = 0x3f3f3f3f;
const int dir[4][2] = {{1,0},{0,1},{0,-1},{-1,0}};
int dist[MAXN][MAXN][11],n,k,a,b,c,A[MAXN][MAXN];
int Dijkstra(){
    memset(dist,0x3f,sizeof(dist));
    dist[1][1][k] = 0;
    priority_queue<pair<int,pair<pair<int,int>,int>>,vector<pair<int,pair<pair<int,int>,int>>>,greater<pair<int,pair<pair<int,int>,int>>>> que;
    que.push(make_pair(dist[1][1][k],make_pair(make_pair(1,1),k)));
    while(!que.empty()){
        auto p = que.top();
        que.pop();
        if(dist[p.second.first.first][p.second.first.second][p.second.second]!=p.first) continue;
        int cx = p.second.first.first, cy = p.second.first.second, ck = p.second.second;
        for(int d = 0; d < 4; d++){
            int nx = cx + dir[d][0];
            int ny = cy + dir[d][1];
            int nk = ck - 1;
            if(nx<1 or ny<1 or nx>n or ny>n) continue;
            if(dist[nx][ny][nk]>dist[cx][cy][ck]+(d<2?0:b) and !A[nx][ny]){
                dist[nx][ny][nk] = dist[cx][cy][ck]+(d<2?0:b);
                if(nk) que.push(make_pair(dist[nx][ny][nk],make_pair(make_pair(nx,ny),nk)));
            }
            int need = A[nx][ny]?a:a+c;
            if(dist[nx][ny][k]>dist[cx][cy][ck]+need+(d<2?0:b)){
                dist[nx][ny][k] = dist[cx][cy][ck] + need + (d<2?0:b);
                que.push(make_pair(dist[nx][ny][k],make_pair(make_pair(nx,ny),k)));
            }
        }
    }
    int ret = INF; for(int i = 0; i <= k; i++) ret = min(ret,dist[n][n][i]);
    return ret;
}
int main(){
    scanf("%d %d %d %d %d",&n,&k,&a,&b,&c);
    for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) scanf("%d",&A[i][j]);
    printf("%d\n",Dijkstra());
    return 0;
}
19. 孤岛营救问题

🔗
状压BFS搜索一下就能过

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 11;
const int INF = 0x3f3f3f3f;
const int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int dist[MAXN][MAXN][1<<10],door[MAXN][MAXN][4];
vector<int> key[MAXN][MAXN];
int n,m,num,K;
int check(int x1, int y1, int x2, int y2){
    if(x2-x1==1) return 0;
    if(x2-x1==-1) return 2;
    if(y2-y1==1) return 3;
    if(y2-y1==-1) return 1;
}
int bfs(){
    memset(dist,255,sizeof(dist));
    int msk = 0;
    for(int x : key[1][1]) msk |= (1<<x);
    dist[1][1][msk] = 0;
    queue<pair<pair<int,int>,pair<int,int>>> que;
    que.push(make_pair(make_pair(1,1),make_pair(msk,dist[1][1][msk])));
    while(!que.empty()){
        auto p = que.front();
        que.pop();
        int cx = p.first.first, cy = p.first.second, ck = p.second.first, cd = p.second.second;
        for(int d = 0; d < 4; d++){
            int nx = cx + dir[d][0];
            int ny = cy + dir[d][1];
            int nk = ck;
            if(nx<1 or ny<1 or nx>n or ny>m) continue;
            for(int x : key[nx][ny]) nk |= (1<<x);
            if(door[cx][cy][d]==-1 or (door[cx][cy][d] and ((ck&(1<<(door[cx][cy][d]-1)))==0)) or dist[nx][ny][nk]!=-1) continue;
            dist[nx][ny][nk] = cd + 1;
            if(nx==n and ny==m) return cd + 1;
            que.push(make_pair(make_pair(nx,ny),make_pair(nk,dist[nx][ny][nk])));
        }
    }
    return -1;
}
int main(){
    scanf("%d %d %d %d",&n,&m,&num,&K);
    for(int i = 1; i <= K; i++){
        int x1, y1, x2, y2, op;
        scanf("%d %d %d %d %d",&x1,&y1,&x2,&y2,&op);
        door[x1][y1][check(x1,y1,x2,y2)] = (op?op:-1);
        door[x2][y2][check(x2,y2,x1,y1)] = (op?op:-1);
    }
    int s; scanf("%d",&s);
    for(int i = 1; i <= s; i++){
        int x, y, keynum;
        scanf("%d %d %d",&x,&y,&keynum);
        key[x][y].push_back(keynum-1);
    }
    printf("%d\n",bfs());
    return 0;
}
20. 深海机器人问题

🔗

按题意建图跑费用流就好了

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1111;
const int INF = 0x3f3f3f3f;
int st,ed,n,m,pre[MAXN],preid[MAXN],flow[MAXN],dist[MAXN],vis[MAXN];
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,fee,rev;
    EDGE(){}
    EDGE(int _to, int _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, int cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
int mcmf(){
    int cost = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
int main(){
    ____();
    cin >> st >> ed >> m >> n;
    n++, m++;
    for(int i = 1; i <= m; i++) for(int j = 1; j < n; j++){
        int val; cin >> val;
        ADDEDGE((i-1)*n+j,(i-1)*n+j+1,1,-val);
        ADDEDGE((i-1)*n+j,(i-1)*n+j+1,INF,0);
    }
    for(int i = 1; i <= n; i++) for(int j = 1; j < m; j++){
        int val; cin >> val;
        ADDEDGE((j-1)*n+i,j*n+i,1,-val);
        ADDEDGE((j-1)*n+i,j*n+i,INF,0);
    }
    for(int i = 1; i <= st; i++){
        int k, x, y; cin >> k >> x >> y;
        x++, y++;
        swap(x,y);
        ADDEDGE(S,(y-1)*n+x,k,0);
    }
    for(int i = 1; i <= ed; i++){
        int r, x , y; cin >> r >> x >> y;
        x++, y++;
        swap(x,y);
        ADDEDGE((y-1)*n+x,T,r,0);
    }
    cout << -mcmf() << endl;
    return 0;
}
21. 数字梯形问题

🔗

因为点的经过数量有限制,所以要拆点
三种情况下建图的区别
1.从梯形的顶至底的 \(m\) 条路径互不相交

  • 那么每个点只能经过一次,每条边只能经过一次

2.从梯形的顶至底的 \(m\) 条路径仅在数字结点处相交

  • 那么每个点可以经过无穷次,每条边只能经过一次

3.从梯形的顶至底的 \(m\) 条路径允许在数字结点相交或边相交

  • 点和边都能经过无穷次

建边跑费用流即可,拆点之间建边的费用是负的,这样跑最小费用就好了,正的话跑最大费用

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 4444;
const int INF = 0x3f3f3f3f;
int n,m,pref[MAXN],A[MAXN][MAXN],pre[MAXN],preid[MAXN],flow[MAXN],vis[MAXN],dist[MAXN];
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,fee,rev;
    EDGE(){}
    EDGE(int _to, int _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, int cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
int mcmf(){
    int cost = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
int ID(int x, int y){ return pref[x-1]+y; }
int gao1(){
    for(int i = 0; i < MAXN; i++) G[i].clear();
    for(int i = 1; i <= m; i++) ADDEDGE(S,ID(1,i),1,0);
    for(int i = 1; i <= n; i++) for(int j = 1; j <= i+m-1; j++) ADDEDGE(ID(i,j),ID(i,j)+pref[n],1,-A[i][j]);
    for(int i = 1; i < n; i++){
        for(int j = 1; j <= i+m-1; j++){
            ADDEDGE(ID(i,j)+pref[n],ID(i+1,j),INF,0);
            ADDEDGE(ID(i,j)+pref[n],ID(i+1,j+1),INF,0);
        }
    }
    for(int i = 1; i <= n + m - 1; i++) ADDEDGE(ID(n,i)+pref[n],T,INF,0);
    return -mcmf();
}
int gao2(){
    for(int i = 0; i < MAXN; i++) G[i].clear();
    for(int i = 1; i <= m; i++) ADDEDGE(S,ID(1,i),1,0);
    for(int i = 1; i <= n; i++) for(int j = 1; j <= i+m-1; j++) ADDEDGE(ID(i,j),ID(i,j)+pref[n],INF,-A[i][j]);
    for(int i = 1; i < n; i++){
        for(int j = 1; j <= i+m-1; j++){
            ADDEDGE(ID(i,j)+pref[n],ID(i+1,j),1,0);
            ADDEDGE(ID(i,j)+pref[n],ID(i+1,j+1),1,0);
        }
    }
    for(int i = 1; i <= n + m - 1; i++) ADDEDGE(ID(n,i)+pref[n],T,INF,0);
    return -mcmf();
}
int gao3(){
    for(int i = 0; i < MAXN; i++) G[i].clear();
    for(int i = 1; i <= m; i++) ADDEDGE(S,ID(1,i),1,0);
    for(int i = 1; i <= n; i++) for(int j = 1; j <= i+m-1; j++) ADDEDGE(ID(i,j),ID(i,j)+pref[n],INF,-A[i][j]);
    for(int i = 1; i < n; i++){
        for(int j = 1; j <= i+m-1; j++){
            ADDEDGE(ID(i,j)+pref[n],ID(i+1,j),INF,0);
            ADDEDGE(ID(i,j)+pref[n],ID(i+1,j+1),INF,0);
        }
    }
    for(int i = 1; i <= n + m - 1; i++) ADDEDGE(ID(n,i)+pref[n],T,INF,0);
    return -mcmf();
}
int main(){
    ____();
    cin >> m >> n;
    for(int i = 1; i <= n; i++){
        pref[i] = pref[i-1] + i+m-1;
        for(int j = 1; j <= i+m-1; j++) cin >> A[i][j];
    }
    cout << gao1() << endl << gao2() << endl << gao3() << endl;
    return 0;
}
22. 分配问题

简单的建图,分别以正权和负权跑一边,就是最小值和最大值了

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 333;
const int INF = 0x3f3f3f3f;
int A[MAXN][MAXN],n,pre[MAXN],preid[MAXN],vis[MAXN],flow[MAXN],dist[MAXN];
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,fee,rev;
    EDGE(){}
    EDGE(int _to, int _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, int cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
int mcmf(){
    int cost = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
void build(int tg){
    for(int i = 0; i < MAXN; i++) G[i].clear();
    for(int i = 1; i <= n; i++) ADDEDGE(S,i,1,0);
    for(int i = 1; i <= n; i++) ADDEDGE(i+n,T,1,0);
    for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) ADDEDGE(i,j+n,1,tg*A[i][j]);
}
int main(){
    ____();
    cin >> n;
    for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) cin >> A[i][j];
    build(1); cout << mcmf() << endl;
    build(-1); cout << -mcmf() << endl;
    return 0;
}
23. 运输问题

🔗

和上一题差不多,建图很显然

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 333;
const int INF = 0x3f3f3f3f;
int A[MAXN][MAXN],n,m,pre[MAXN],preid[MAXN],vis[MAXN],flow[MAXN],dist[MAXN];
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,fee,rev;
    EDGE(){}
    EDGE(int _to, int _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, int cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
int mcmf(){
    int cost = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
int cap1[MAXN],cap2[MAXN];
void build(int tg){
    for(int i = 0; i < MAXN; i++) G[i].clear();
    for(int i = 1; i <= n; i++) ADDEDGE(S,i,cap1[i],0);
    for(int i = 1; i <= m; i++) ADDEDGE(i+n,T,cap2[i],0);
    for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) ADDEDGE(i,j+n,INF,tg*A[i][j]);
}
int main(){
    ____();
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> cap1[i];
    for(int i = 1; i <= m; i++) cin >> cap2[i];
    for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) cin >> A[i][j];
    build(1); cout << mcmf() << endl;
    build(-1); cout << -mcmf() << endl;
    return 0;
}
24. 负载平衡问题

🔗

1.源点向每个位置连边,容量是货物数量,费用是\(0\)

2.每个位置向汇点建边,容量是平均货物数量,费用是\(0\)

3.每个位置向它的左右两边建边,容量\(INF\),费用\(1\)

跑费用流

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 111;
const int INF = 0x3f3f3f3f;
int n,A[MAXN],ave,pre[MAXN],preid[MAXN],vis[MAXN],flow[MAXN],dist[MAXN];
#define S 0
#define T MAXN - 1
struct EDGE{
    int to,cap,fee,rev;
    EDGE(){}
    EDGE(int _to, int _cap, int _fee, int _rev){
        to = _to; cap = _cap;
        fee = _fee; rev = _rev;
    }
};
vector<EDGE> G[MAXN];
void ADDEDGE(int u, int v, int cap, int fee){
    G[u].emplace_back(EDGE(v,cap,fee,(int)G[v].size()));
    G[v].emplace_back(EDGE(u,0,-fee,(int)G[u].size()-1));
}
bool spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[S] = 0;
    flow[S] = INF;
    memset(vis,0,sizeof(vis));
    queue<int> que;
    que.push(S);
    while(!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            auto e = G[u][i];
            if(!e.cap or dist[e.to]<=dist[u]+e.fee) continue;
            dist[e.to] = dist[u] + e.fee;
            flow[e.to] = min(e.cap,flow[u]);
            pre[e.to] = u; preid[e.to] = i;
            if(!vis[e.to]){
                vis[e.to] = 1;
                que.push(e.to);
            }
        }
    }
    return dist[T]!=INF;
}
int mcmf(){
    int cost = 0;
    while(spfa()){
        int u = T;
        cost += dist[T] * flow[T];
        while(u!=S){
            int p = pre[u], id = preid[u];
            G[p][id].cap -= flow[T];
            G[u][G[p][id].rev].cap += flow[T];
            u = pre[u];
        }
    }
    return cost;
}
void build(){
    int tot = 0; for(int i = 1; i <= n; i++) tot += A[i];
    ave = tot / n;
    for(int i = 1; i <= n; i++){
        ADDEDGE(S,i,A[i],0);
        ADDEDGE(i,T,ave,0);
        ADDEDGE(i,i==n?1:i+1,INF,1);
        ADDEDGE(i,i==1?n:i-1,INF,1);
    }
    
}
int main(){
    ____();
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> A[i];
    build(); cout << mcmf() << endl;
    return 0;
}
posted @ 2020-04-29 10:33  _kiko  阅读(319)  评论(0编辑  收藏  举报