金牌导航-网络流模型及应用

网络流模型及应用

例题A题解

直接对于每个限制连边,然后跑最小割,最小割等于最大流。

例题A代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f =1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 5e4 + 10,maxm = 5e5 + 10, INF = 0x3f3f3f3f;
int n, m;

int head[maxn], tot = 1;
struct edge{
    int to, nexte, cap, flow;
    edge(int to = 0,int ne = 0,int ca = 0,int fl = 0):to(to),nexte(ne),cap(ca),flow(fl){}
}e[maxm * 2];
void add(int u,int v,int cap){e[++tot] = edge(v,head[u],cap);head[u] = tot;}
void addd(int u,int v,int cap){add(u,v,cap);add(v, u, 0);}

int dep[maxn], cur[maxn];
int dfs(int u,int flow,int T){
    // printf("%lld\n",u);
    if(u == T)return flow;
    int rest = 0, tmp = 0;
    for(int i = cur[u];i && flow;i = e[i].nexte){
        cur[u] = i; int v = e[i].to;
        if(e[i].cap - e[i].flow > 0 && (dep[v] == dep[u] + 1)){
            tmp = dfs(v,min(flow,e[i].cap - e[i].flow),T);
            if(tmp == 0)dep[v] = INF;
            e[i].flow += tmp; e[i ^ 1].flow -= tmp;
            flow -= tmp;rest += tmp;
            if(!flow)return rest;
        }
    }
    return rest;
}
bool bfs(int S,int T){
    queue<int> que;
    for(int i = 1;i <= T;i++){dep[i] = INF;cur[i] = 0;}
    que.push(S);dep[S] = 1; cur[S] = head[S];
    while(!que.empty()){
        int u = que.front(); que.pop();
        for(int i = head[u];i;i = e[i].nexte){
            int v = e[i].to;
            if(e[i].cap - e[i].flow > 0 && dep[v] == INF){
                que.push(v);
                cur[v] = head[v];
                dep[v] = dep[u] + 1;
                if(v == T)return 1;
            }
        }
    }
    return 0;
}
int Dinic(int S,int T){
    int mxflow = 0;
    while(bfs(S,T)){mxflow += dfs(S,INF,T);}
    return mxflow;
}


signed main(){
    n = read(); m =read();int S = n + 1, T = n + 2;
    for(int i = 1;i <= n;i++){
        addd(S,i,read());addd(i,T,read());
    }
    for(int i = 1;i <= m;i++){
        int x = read(), y = read(), z = read();
        addd(x,y,z);addd(y,x,z);
    }
    printf("%lld\n",Dinic(S,T));
    return 0;
}

例题B题解

先提前加好所有贡献
将所有点分别从源点和向汇点连边,然后断开哪条边就是不选哪个。
这里钦定从源点连边代表选择文科,否则选择理科。
然后对于每个特殊限制,新建一个节点,然后按照文理分开连边即可。

例题B代码

#include<bits/stdc++.h>
// #define int long long
using namespace std;
inline int read(){
    int x = 0, f =1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 5e2 + 10,maxm = 5e5 + 10, INF = 0x3f3f3f3f;
int n, m;
int id[maxn][maxn], total;

int head[maxm / 10], tot = 1;
struct edge{
    int to, nexte, cap, flow;
    edge(int to = 0,int ne = 0,int ca = 0,int fl = 0):to(to),nexte(ne),cap(ca),flow(fl){}
}e[maxm * 2];
void add(int u,int v,int cap){e[++tot] = edge(v,head[u],cap);head[u] = tot;}
void addd(int u,int v,int cap){add(u,v,cap);add(v, u, 0);}

int dep[maxm / 10], cur[maxm / 10];
int dfs(int u,int flow,int T){
    // printf("%lld\n",u);
    if(u == T)return flow;
    int rest = 0, tmp = 0;
    for(int i = cur[u];i && flow;i = e[i].nexte){
        cur[u] = i; int v = e[i].to;
        if(e[i].cap - e[i].flow > 0 && (dep[v] == dep[u] + 1)){
            tmp = dfs(v,min(flow,e[i].cap - e[i].flow),T);
            if(tmp == 0)dep[v] = INF;
            e[i].flow += tmp; e[i ^ 1].flow -= tmp;
            flow -= tmp;rest += tmp;
            if(!flow)return rest;
        }
    }
    return rest;
}
bool bfs(int S,int T){
    queue<int> que;
    for(int i = 1;i <= total;i++){dep[i] = INF;cur[i] = 0;}
    que.push(S);dep[S] = 1; cur[S] = head[S];
    while(!que.empty()){
        int u = que.front(); que.pop();
        for(int i = head[u];i;i = e[i].nexte){
            int v = e[i].to;
            if(e[i].cap - e[i].flow > 0 && dep[v] == INF){
                que.push(v);
                cur[v] = head[v];
                dep[v] = dep[u] + 1;
                if(v == T)return 1;
            }
        }
    }
    return 0;
}
int Dinic(int S,int T){
    int mxflow = 0;
    while(bfs(S,T)){mxflow += dfs(S,INF,T);}
    return mxflow;
}


signed main(){
    n = read(); m = read();
    int S = ++total;int T = ++total, sum = 0, x = 0;
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= m;j++){
            id[i][j] = ++total;
            sum += (x = read());
            addd(S,id[i][j],x);
        }
    }
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= m;j++){
            sum += (x = read());
            addd(id[i][j],T,x);
        }
    }
    for(int i = 1;i < n;i++){
        for(int j = 1;j <= m;j++){
            int y = ++total;
            sum += (x = read());
            addd(S,y,x);addd(y,id[i][j],INF);addd(y,id[i + 1][j],INF);
        }
    }
    for(int i = 1;i < n;i++){
        for(int j = 1;j <= m;j++){
            int y = ++total;
            sum += (x = read());
            addd(y,T,x);addd(id[i][j],y,INF);addd(id[i + 1][j],y,INF);
        }
    }
    for(int i = 1;i <= n;i++){
        for(int j = 1;j < m;j++){
            int y = ++total;
            sum += (x = read());
            addd(S,y,x);addd(y,id[i][j],INF);addd(y,id[i][j + 1],INF);
        }
    }
    for(int i = 1;i <= n;i++){
        for(int j = 1;j < m;j++){
            int y = ++total;
            sum += (x = read());
            addd(y,T,x);addd(id[i][j],y,INF);addd(id[i][j + 1],y,INF);
        }
    }
    printf("%d\n",sum - Dinic(S,T));
    return 0;
}

例题C题解

必须知道的是,这个要求最大权闭合子图。
然后先加上所有的收益,然后考虑割掉哪些就是选择哪些。
尝试这么建图:所有实验从源点连边,容量是收益;所有实验向相应器材连边,容量为 \(\inf\),保证不会被割断;所有器材向汇点连边,容量是费用。
这样建图之后,答案就是总收益减去最小割(最大流)。
然后考虑怎么输出方案。
想到,我们让一个实验与源点被割断,相当于减去这个实验的贡献,也就是说,不选择这个实验。
同样的,如果将一个器材与汇点割断,相当于接受这个器材带来的代价,也就是说,选择这个器材。
发现,所有被选择的实验和器材,一定与源点联通。然后在跑完最大流之后判断一下哪些点与源点相连(分层图有编号)就是答案。

例题C代码

#include<bits/stdc++.h>
// #define int long long
using namespace std;
inline int read(){
    int x = 0, f =1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 5e2 + 10,maxm = 5e5 + 10, INF = 0x3f3f3f3f;
int n, m;
int id[maxn][maxn], total;

int head[maxm / 10], tot = 1;
struct edge{
    int to, nexte, cap, flow;
    edge(int to = 0,int ne = 0,int ca = 0,int fl = 0):to(to),nexte(ne),cap(ca),flow(fl){}
}e[maxm * 2];
void add(int u,int v,int cap){e[++tot] = edge(v,head[u],cap);head[u] = tot;}
void addd(int u,int v,int cap){add(u,v,cap);add(v, u, 0);}

int dep[maxm / 10], cur[maxm / 10];
int dfs(int u,int flow,int T){
    // printf("%lld\n",u);
    if(u == T)return flow;
    int rest = 0, tmp = 0;
    for(int i = cur[u];i && flow;i = e[i].nexte){
        cur[u] = i; int v = e[i].to;
        if(e[i].cap - e[i].flow > 0 && (dep[v] == dep[u] + 1)){
            tmp = dfs(v,min(flow,e[i].cap - e[i].flow),T);
            if(tmp == 0)dep[v] = INF;
            e[i].flow += tmp; e[i ^ 1].flow -= tmp;
            flow -= tmp;rest += tmp;
            if(!flow)return rest;
        }
    }
    return rest;
}
bool bfs(int S,int T){
    queue<int> que;
    for(int i = 1;i <= T;i++){dep[i] = INF;cur[i] = 0;}
    que.push(S);dep[S] = 1; cur[S] = head[S];
    while(!que.empty()){
        int u = que.front(); que.pop();
        for(int i = head[u];i;i = e[i].nexte){
            int v = e[i].to;
            if(e[i].cap - e[i].flow > 0 && dep[v] == INF){
                que.push(v);
                cur[v] = head[v];
                dep[v] = dep[u] + 1;
                if(v == T)return 1;
            }
        }
    }
    return 0;
}
int Dinic(int S,int T){
    int mxflow = 0;
    while(bfs(S,T)){mxflow += dfs(S,INF,T);}
    return mxflow;
}
signed main(){
    m =  read(); n = read();int ans = 0;
    int S = n + m + 1, T = n + m + 2;
    for(int i = 1;i <= m;i++){
        int x = read();ans += x;addd(S,i,x);
        char tools[10000];
        memset(tools,0,sizeof(tools));
        cin.getline(tools,10000);
        int ulen = 0,tool;
        while(sscanf(tools + ulen,"%d",&tool) == 1){
            addd(i,tool + m,INF);
            if(tool == 0)ulen++;
            else while(tool){tool /= 10;ulen++;}
            ulen++;
        }
    }
    for(int i = 1;i <= n;i++)addd(i + m,T,read());
    ans = ans - Dinic(S,T);Dinic(S,T);
    for(int i = 1;i <= m;i++){
        if(dep[i] != INF)printf("%d ",i);
    }
    puts("");
    for(int i = 1;i <= n;i++){
        if(dep[i + m] != INF)printf("%d ",i);
    }
    printf("\n%d\n",ans);
    return 0;
}

例题D题解

先跑一遍最小割(最大流),然后考虑被分到 \(S\)\(T\) 的点总和,分别设为集合 \(A\) 和集合 \(B\)
不难发现,正常情况下,\(A\cup B=U\),但是什么时候会不正常呢?
当且仅当它同时断开了向源点和汇点的边对吧:)
这个时候我们发现,这个点断开哪条边都能够使得 \(S\to T\) 不联通,也就是说,这个点既可以归到 \(A\),也可以归到 \(B\)。这个时候,最小割不唯一。

例题D代码

#include<bits/stdc++.h>
// #define int long long
using namespace std;
inline int read(){
    int x = 0, f =1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 5e2 + 10,maxm = 5e5 + 10, INF = 0x3f3f3f3f;
int n, m;

int head[maxm / 10], tot = 1;
struct edge{
    int to, nexte, cap, flow;
    edge(int to = 0,int ne = 0,int ca = 0,int fl = 0):to(to),nexte(ne),cap(ca),flow(fl){}
}e[maxm * 2];
void add(int u,int v,int cap){e[++tot] = edge(v,head[u],cap);head[u] = tot;}
void addd(int u,int v,int cap){add(u,v,cap);add(v, u, 0);}

int dep[maxm / 10], cur[maxm / 10];
int dfs(int u,int flow,int T){
    // printf("%lld\n",u);
    if(u == T)return flow;
    int rest = 0, tmp = 0;
    for(int i = cur[u];i && flow;i = e[i].nexte){
        cur[u] = i; int v = e[i].to;
        if(e[i].cap - e[i].flow > 0 && (dep[v] == dep[u] + 1)){
            tmp = dfs(v,min(flow,e[i].cap - e[i].flow),T);
            if(tmp == 0)dep[v] = INF;
            e[i].flow += tmp; e[i ^ 1].flow -= tmp;
            flow -= tmp;rest += tmp;
            if(!flow)return rest;
        }
    }
    return rest;
}
bool bfs(int S,int T){
    queue<int> que;
    for(int i = 1;i <= n;i++){dep[i] = INF;cur[i] = 0;}
    que.push(S);dep[S] = 1; cur[S] = head[S];
    while(!que.empty()){
        int u = que.front(); que.pop();
        for(int i = head[u];i;i = e[i].nexte){
            int v = e[i].to;
            if(e[i].cap - e[i].flow > 0 && dep[v] == INF){
                que.push(v);
                cur[v] = head[v];
                dep[v] = dep[u] + 1;
                if(v == T)return 1;
            }
        }
    }
    return 0;
}
int Dinic(int S,int T){
    int mxflow = 0;
    while(bfs(S,T)){mxflow += dfs(S,INF,T);}
    return mxflow;
}

bool vis[maxn];

int S, T;
void dfs1(int u,int opt){
    // printf("u = %d,opt = %d\n",u,opt);
    vis[u] = 1;
    for(int i = head[u];i;i = e[i].nexte){
        int v = e[i].to;
        // printf("v = %d,i = %d\n",v,i);
        if(!vis[v] && (e[i ^ opt].cap > e[i ^ opt].flow))dfs1(v,opt);
    }
}
void solve(){
    memset(head,0,sizeof(head));tot = 1;memset(vis,0,sizeof(vis));
    for(int i = 1;i <= m;i++){
        int u = read(), v = read(), w = read();
        add(u, v, w);add(v, u, w);
    }
    int ans = Dinic(S,T);
    dfs1(S,0);dfs1(T,1);
    for(int i = 1;i <= n;i++)
        if(!vis[i]){puts("AMBIGUOUS");return;}
    puts("UNIQUE");
    return;
}

signed main(){
    while((n = read(), m = read(), S = read(),
       T = read()),(n && m && S && T))solve();
    return 0;
}

例题E题解

最大密度子图模板。
最大密度子图定义:从一张图中选出一个点集 \(V'\),只保留两个端点都在选择的点集的边,称为边集 \(E'\),最大的 \(\frac{|E'|}{|V'|}\) 就是最大密度子图。
首先想到一个贪心,就是在确定了点集之后,边集一定是对应的导出子图(就是将所有两个端点都在选择的点集中的边选择后得到的子图),换句话说,确定了点集,边集是确定的
然后如果我们设 \(k=\frac{|E'|}{|V'|}\),那么我们就是要求 \(\max k\)
不难想到01分数规划。具体而言,我们每次要判定一个 \(k\),需要知道是否存在一个点集 \(|V'|\),使得 \(|E'|-k\times|V'|\ge0\)
\(deg_u\) 表示与 \(u\) 相连的边数
在得到了刚刚的贪心之后,尝试转化一下柿子:

\[|E'|-k\times|V'|\ge0\\ \iff\frac{\sum_{u\in{V'}}deg_u}{2}-\sum_{u\in{V'},v\notin{V'},(u,v)\in{V}}1-k\times\sum_{u\in{V'}}1\ge 0\\ \iff\sum_{u\in{V'}}(deg_u-2\times k)-\sum_{u\in{V'},v\notin{V'},(u,v)\in{V}}1-k\times\sum_{u\in{V'}}1\ge 0\\ \iff\sum_{u\in{V'}}(2\times k-deg_u)+\sum_{u\in{V'},v\notin{V'},(u,v)\in{V}}1-k\times\sum_{u\in{V'}}1\le 0 \]

不难发现,选择一个点,相当于花费 \(2\times k-deg_u\) 的贡献(不选花费就是0,但是选择的贡献可能是负的),而如果有一对点一个被选择另一个不被选择就有 \(1\) 的贡献,现在要最小贡献。
这东西显然是最小割。
不过需要注意,最小割是没办法做负权的,于是尝试加一个大数。然后最后的时候减去这个大数就完事了。
最后需要注意的是精度,理论上把二分的精度设成 \(\frac{1}{n^2}\) 就够了,但是其他的精度需要到 \(10^{-12}\) 的(我不说是谁被卡在98pts,不认识不认识)

例题E代码

#include<bits/stdc++.h>
// #define int long long
using namespace std;
inline int read(){
    int x = 0, f =1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e3 + 10,maxm = 5e5 + 10, INF = 0x3f3f3f3f;
const double eps = 1e-12;
int n, m;
// double eps;

int head[maxm / 10], tot = 1;
struct edge{
    int to, nexte;double cap, flow;
    edge(int to = 0,int ne = 0,double ca = 0,double fl = 0):to(to),nexte(ne),cap(ca),flow(fl){}
}e[maxm * 2];
void add(int u,int v,double cap){e[++tot] = edge(v,head[u],cap);head[u] = tot;}
void addd(int u,int v,double cap){add(u,v,cap);add(v, u, 0);}

int dep[maxm / 10], cur[maxm / 10];
double dfs(int u,double flow,int T){
    // printf("%lld\n",u);
    if(u == T || flow <= eps)return flow;
    double rest = 0, tmp = 0;
    for(int i = cur[u];i && flow;i = e[i].nexte){
        cur[u] = i; int v = e[i].to;
        if(e[i].cap - e[i].flow > 0 && (dep[v] == dep[u] + 1)){
            tmp = dfs(v,min(flow,e[i].cap - e[i].flow),T);
            if(tmp == 0)dep[v] = INF;
            e[i].flow += tmp; e[i ^ 1].flow -= tmp;
            flow -= tmp;rest += tmp;
            if(flow <= eps)return rest;
        }
    }
    return rest;
}
bool bfs(int S,int T){
    queue<int> que;
    for(int i = 1;i <= T;i++){dep[i] = INF;cur[i] = 0;}
    que.push(S);dep[S] = 1; cur[S] = head[S];
    while(!que.empty()){
        int u = que.front(); que.pop();
        for(int i = head[u];i;i = e[i].nexte){
            int v = e[i].to;
            if(e[i].cap - e[i].flow > 0 && dep[v] == INF){
                que.push(v);cur[v] = head[v];dep[v] = dep[u] + 1;
                if(v == T)return 1;
            }
        }
    }
    return 0;
}
double Dinic(int S,int T){
    double mxflow = 0;
    while(bfs(S,T)){mxflow += dfs(S,INF,T);}
    return mxflow;
}

pair<int,int> edg[maxm / 100];
int deg[maxn];
int S, T;
double getans(double mid){
    tot = 1;memset(head,0,sizeof(head));
    for(int i = 1;i <= m;i++)add(edg[i].first,edg[i].second,1),add(edg[i].second,edg[i].first,1);
    for(int i = 1;i <= n;i++){addd(S,i,m * 1.0);addd(i,T,1.0 * m + mid * 2.0 - deg[i]);}
    double x = Dinic(S,T);
    return x;
}

bool vis[maxn];
int ans = 0;
void dfs1(int u){
    vis[u] = 1;if(u != S)ans++;
    for(int i = head[u];i;i = e[i].nexte){
        int v = e[i].to;
        if(!vis[v] && e[i].cap - e[i].flow > eps) dfs1(v);
    }
}

void solve(){
    S = n + 1,T = n + 2;memset(deg,0,sizeof(deg));
    memset(vis,0,sizeof(vis));
    memset(edg,0,sizeof(edg));
    for(int i = 1;i <= m;i++){
        edg[i] = make_pair(read(),read());
        deg[edg[i].first]++;deg[edg[i].second]++;
    }
    double l = 0, r = m * 1.0, mid = 0, eps1 = 1.0 / n / n;
    while(r - l > eps1){
        mid = (r + l) / 2.0;
        if(m * n - getans(mid) > eps)l = mid;
        else r = mid;
    }
    getans(l); dfs1(S);

    if(ans == 0){puts("1\n1\n");return;}
    printf("%d\n",ans);
    for(int i = 1;i <= n;i++)if(vis[i])printf("%d\n",i);
    return;
}

signed main(){
    while(scanf("%d%d",&n,&m) != EOF)solve();
    return 0;
}

例题F题解

和例题C极其相似的一道题,某种意义上,这两道题算重题。
题解的话去看例题C题解,看完了这道题就会了。

例题F代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 110, INF = 0x3f3f3f3f, maxm = 3e5 + 10;

int n, m;
int art, sci;
int sart, ssci;

int head[maxm], tot = 1;
struct edge{
    int to, nexte, cap, flow;
    edge(int to = 0,int ne = 0,int cap = 0,int fl = 0):to(to),nexte(ne),cap(cap),flow(fl){}
}e[maxm * 10];
void add(int u,int v,int cap){e[++tot] = edge(v,head[u],cap);head[u] = tot;}
void addd(int u,int v,int cap){add(u, v, cap);add(v, u, 0);}
    // printf("%d -> %d, cap = %d\n",u, v, cap);

const int dx[5] = {0,-1, 1, 0, 0};
const int dy[5] = {0, 0, 0,-1, 1};

queue<int> que;
bool book[maxm];
int dis[maxm], cur[maxm];
int dfs(int u,int flow,int T){
    if(u == T || !flow)return flow;
    int res = 0;
    for(int i = head[u];i;i = e[i].nexte){
        int v = e[i].to;
        if(dis[v] == dis[u] + 1 && e[i].cap > e[i].flow){
            int tmp = dfs(v,min(flow,e[i].cap - e[i].flow),T);
            if(!tmp)dis[v] = INF;
            e[i].flow += tmp;e[i ^ 1].flow -= tmp;
            res += tmp;flow -= tmp;
        }
    }
    return res;
}
bool bfs(int S,int T){
    while(!que.empty())que.pop();memset(cur,0,sizeof(cur));
    memset(book,0,sizeof(book));memset(dis,0x3f,sizeof(dis));
    cur[S] = head[S];dis[S] = 0;que.push(S);
    while(!que.empty()){
        int u = que.front();que.pop();if(book[u])continue;book[u] = 1;
        for(int i = head[u];i;i = e[i].nexte){
            int v = e[i].to;
            if(dis[v] == INF && e[i].cap > e[i].flow){
                dis[v] = dis[u] + 1;cur[v] = head[v];
                que.push(v);
            }
        }
    }
    return dis[T] != INF;
}
int maxflow(int S,int T){
    int mxflow = 0;
    while(bfs(S,T))mxflow += dfs(S,INF,T);
    return mxflow;
}
int pos[maxn][maxn][3], cntpoint;
signed main(){
    n = read(); m = read();int S = ++cntpoint, T = ++cntpoint, sum = 0;
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            for(int k = 0;k < 3;k++){
                pos[i][j][k] = ++cntpoint;
                // printf("pos[%d][%d][%d]=%d\n",i,j,k,cntpoint);
            }
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++){
            sum += (art = read());
            // puts("type 111111111111");
            addd(S,pos[i][j][0],art);
        }
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++){
            sum += (sci = read());
            // puts("type 222222222222");
            addd(pos[i][j][0],T,sci);
        }
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++){
            sum += (sart = read());
            // puts("type 333333333333");
            addd(S,pos[i][j][1],sart);
        }
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++){
            sum += (ssci = read());
            // puts("type 444444444444");
            addd(pos[i][j][2],T,ssci);
        }
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            for(int l = 0;l < 5;l++){
                int nx = dx[l] + i, ny = dy[l] + j;
                if(nx < 1 || ny < 1 || nx > n || ny > m)continue;
                // puts("type 55555555555");
                addd(pos[nx][ny][1],pos[i][j][0], INF);
                addd(pos[i][j][0],pos[nx][ny][2],INF);
            }
    printf("%d\n",sum - maxflow(S,T));
    // for(int u = 1;u <= cntpoint;u++)
    //     for(int i = head[u];i;i = e[i].nexte)
    //         printf("i = %d,%d -> %d, cap = %d, flow = %d\n",i,u,e[i].to,e[i].cap,e[i].flow);
    return 0;
}

例题G题解

不难发现,横向的炮台互不影响,纵向的炮台互不影响,横纵能够影响的只可能是炮弹轨迹相交。
还有,对于一个炮台,最优的决策一定是从这个炮台的位置到他能够到达的第一个最大值位置中选择一个,否则不优。
于是思路有了,对于每个位置拆成横向和纵向两个,并且连上 \(\inf\) 的边权,然后对于一条轨迹的位置 \((i,j)\to (i+dx,j+dy)\),连接边权为 \(mx-a_{i,j}\),其中 \(mx\) 是这条轨迹能够到达的最大值。
然后跑最小割(最大流)即可。

例题G代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 5e2 + 10,maxm = 5e5 + 10, INF = 0x3f3f3f3f;
int n, m;
int id[2][maxn][maxn], total;

int head[maxm], tot = 1;
struct edge{
    int to, nexte, cap, flow;
    edge(int to = 0,int ne = 0,int ca = 0,int fl = 0):to(to),nexte(ne),cap(ca),flow(fl){}
}e[maxm * 2];
void add(int u,int v,int cap){e[++tot] = edge(v,head[u],cap);head[u] = tot;}
void addd(int u,int v,int cap){add(u,v,cap);add(v, u, 0);}

int dep[maxm], cur[maxm];
int dfs(int u,int flow,int T){
    // printf("%lld\n",u);
    if(u == T)return flow;
    int rest = 0, tmp = 0;
    for(int i = cur[u];i && flow;i = e[i].nexte){
        cur[u] = i; int v = e[i].to;
        if(e[i].cap - e[i].flow > 0 && (dep[v] == dep[u] + 1)){
            tmp = dfs(v,min(flow,e[i].cap - e[i].flow),T);
            if(tmp == 0)dep[v] = INF;
            e[i].flow += tmp; e[i ^ 1].flow -= tmp;
            flow -= tmp;rest += tmp;
            if(!flow)return rest;
        }
    }
    return rest;
}
bool bfs(int S,int T){
    queue<int> que;
    for(int i = 1;i <= total;i++){dep[i] = INF;cur[i] = 0;}
    que.push(S);dep[S] = 1; cur[S] = head[S];
    while(!que.empty()){
        int u = que.front(); que.pop();
        for(int i = head[u];i;i = e[i].nexte){
            int v = e[i].to;
            if(e[i].cap - e[i].flow > 0 && dep[v] == INF){
                que.push(v);
                cur[v] = head[v];
                dep[v] = dep[u] + 1;
                if(v == T)return 1;
            }
        }
    }
    return 0;
}
int Dinic(int S,int T){
    int mxflow = 0;
    while(bfs(S,T)){mxflow += dfs(S,INF,T);}
    return mxflow;
}

int a[maxn][maxn], mx[maxn][maxn];
const int dx[4] = {-1, 1, 0, 0};
const int dy[4] = { 0, 0,-1, 1};

signed main(){
    n = read(); m = read();
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++){
            a[i][j] = read();
            id[0][i][j] = ++total; id[1][i][j] = ++total;
            addd(id[0][i][j],id[1][i][j],INF);
        }
    int S = ++total;int T = ++total, ans = 0;
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++){
            if(a[i][j] < 0){
                int x = 0, posi = i, posj = j;
                int nx = i, ny = j;
                int opt = -a[i][j] - 1;
                while(1){
                    nx += dx[opt];ny += dy[opt];
                    if(!(nx > 0 && nx <= n && ny > 0 && ny <= m))break;
                    if(a[nx][ny] > x){
                        x = a[nx][ny];
                        posi = nx;posj = ny;
                    }
                }
                if(posi == i && posj == j)continue;
                mx[i][j] = x;ans += x;
                if(a[i][j] >= -2){
                    nx = posi;ny = posj;a[i][j] = 0;
                    while(1){
                        addd(id[1][nx][ny],id[1][nx - dx[opt]][ny - dy[opt]],mx[i][j] - a[nx - dx[opt]][ny - dy[opt]]);
                        nx -= dx[opt];ny -= dy[opt];
                        if(nx == i && ny == j)break;
                    }
                    addd(id[1][i][j],T,INF);
                }
                else{
                    nx = i;ny = j;a[i][j] = 0;
                    while(1){
                        addd(id[0][nx][ny],id[0][nx + dx[opt]][ny + dy[opt]],mx[i][j] - a[nx][ny]);
                        nx += dx[opt];ny += dy[opt];
                        if(nx == posi && ny == posj)break;
                    }
                    addd(S,id[0][i][j],INF);
                }
            }
        }
    // for(int i = 1;i <= n;i++){
    //     for(int j = 1;j <= m;j++){
    //         printf("(%d,%d)id = %d, %d\n",i,j,id[0][i][j],id[1][i][j]);
    //     }
    // }
    // for(int i = 2;i <= tot;i += 2){
    //     printf("%d %d %d %d\n",e[i ^ 1].to,e[i].to,e[i].cap,e[i].flow);
    // }
    printf("%d\n",ans - Dinic(S,T));
    return 0;
}
/*
6 5
-4 0 9 3 2
-3 -4 4 7 6
1 0 -1 -4 8
0 -2 -2 -2 -1
-4 3 6 0 0
9 0 8 0 -1

*/

这里附带一个数据生成器:

#include<bits/stdc++.h>
using namespace std;
bool book[100][100][4];
signed main(){
    srand(time(0));
    mt19937 rnd(rand());
    int n = 50, m = 50;
    printf("%d %d\n",n,m);
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= m;j++){
            int sed = rnd() % 15;
            if(0 <= sed && sed <= 3){
                int chk = true;
                if(sed == 0){
                    if(book[i][j][0]){chk = false;}
                    else{
                        book[i][j][0] = 1;
                        for(int k = i + 1;k <= n;k++){book[k][j][0] = 1;}
                        for(int k = j + 1;k <= m;k++){book[i][k][2] = 1;}
                    }
                }
                if(sed == 1){
                    if(book[i][j][1])chk = false;
                    else{
                        for(int k = i;k <= n;k++)book[k][j][0] = book[k][j][1] = book[k][j][2] = book[k][j][3] = 1;
                        for(int k = j + 1;k <= m;k++)book[i][k][2] = 1;
                    }
                }
                if(sed == 2){
                    if(book[i][j][2])chk = false;
                    else{
                        for(int k = j + 1;k <= m;k++){book[i][k][2] = 1;}
                    }
                }
                if(sed == 3){
                    if(book[i][j][3])chk = false;
                    for(int k = i + 1;k <= n;k++){book[k][j][0] = 1;}
                    for(int k = j + 1;k <= m;k++)book[i][k][0] = book[i][k][1] = book[i][k][2] = book[i][k][3] = 1;
                }
                printf("%d ",chk * (-sed - 1));
            }
            if(4 <= sed && sed <= 6){
                printf("%d ",0);
            }
            if(7 <= sed && sed <= 14){
                printf("%d ",rnd() % 1000);
            }
        }
        puts("");
    }
    return 0;
}

例题H题解

首先转化下答案,变成

\[\sum_{i=1}^n\sum_{j=1}^na_i\times a_j\times b_{i,j}-\sum_{i=1}^na_i\times c_i \]

然后发现可以左边放 \(n^2\) 个点 \((i,j)\),连向源点,容量是 \(b_{i,j}\),割断表示不让这个贡献出现在答案中。
右边放 \(n\) 个点,连向汇点,容量是 \(c_i\),割断表示选择这个点并接受这个点带来的代价。
然后 \(\sum_{i=1}^n\sum_{j=1}^nb_{i,j}\) 最小割(最大流)就是答案。

例题H代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e3 + 10,maxm = 1e6 + 10, INF = 0x3f3f3f3f;
int n, m;
int total;

int head[maxm], tot = 1;
struct edge{
    int to, nexte, cap, flow;
    edge(int to = 0,int ne = 0,int ca = 0,int fl = 0):to(to),nexte(ne),cap(ca),flow(fl){}
}e[maxm * 2];
void add(int u,int v,int cap){e[++tot] = edge(v,head[u],cap);head[u] = tot;}
void addd(int u,int v,int cap){add(u,v,cap);add(v, u, 0);}

int dep[maxm], cur[maxm];
int dfs(int u,int flow,int T){
    // printf("%lld\n",u);
    if(u == T)return flow;
    int rest = 0, tmp = 0;
    for(int i = cur[u];i && flow;i = e[i].nexte){
        cur[u] = i; int v = e[i].to;
        if(e[i].cap - e[i].flow > 0 && (dep[v] == dep[u] + 1)){
            tmp = dfs(v,min(flow,e[i].cap - e[i].flow),T);
            if(tmp == 0)dep[v] = INF;
            e[i].flow += tmp; e[i ^ 1].flow -= tmp;
            flow -= tmp;rest += tmp;
            if(!flow)return rest;
        }
    }
    return rest;
}
bool bfs(int S,int T){
    queue<int> que;
    for(int i = 1;i <= total;i++){dep[i] = INF;cur[i] = 0;}
    que.push(S);dep[S] = 1; cur[S] = head[S];
    while(!que.empty()){
        int u = que.front(); que.pop();
        for(int i = head[u];i;i = e[i].nexte){
            int v = e[i].to;
            if(e[i].cap - e[i].flow > 0 && dep[v] == INF){
                que.push(v);
                cur[v] = head[v];
                dep[v] = dep[u] + 1;
                if(v == T)return 1;
            }
        }
    }
    return 0;
}
int Dinic(int S,int T){
    int mxflow = 0;
    while(bfs(S,T)){mxflow += dfs(S,INF,T);}
    return mxflow;
}
int a[maxn], b[maxn][maxn], c[maxn];
int id[maxn][maxn];
signed main(){
    n = read();int ans = 0;
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= n;j++){
            ans += (b[i][j] = read());
            id[i][j] = ++total;
        }
    for(int i = 1;i <= n;i++){
        id[i][0] = ++total;
        c[i] = read();
    }
    int S = ++total;int T = ++total;
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= n;j++){
            addd(S,id[i][j],b[i][j]);
            addd(id[i][j],id[i][0],INF);
            addd(id[i][j],id[j][0],INF);
        }
        addd(id[i][0],T,c[i]);
    }
    printf("%d\n",ans - Dinic(S,T));
    return 0;
}
posted @ 2023-12-12 12:31  Call_me_Eric  阅读(36)  评论(0)    收藏  举报
Live2D