补 第三场多校杭电 费用流 K Subsequence

K Subsequence

这个题目是这个人想吃东西,但是他每次吃的都是他的美味值都必须不递减,可以吃k次,问这个最大的美味值是多少。

这个是一个比较明显的费用流,建图也很好建,但是呢,这个题目卡spfa费用流,所以要用dij的费用流。

刚刚是第一种方法,第二种方法就是优化,减少很多边来优化这个复杂度。

因为每次的美味值都必须不递减,

所以比如我们给x建边,w[x]=a  后面我们建比a大的w[y]=b 如果后面有比b大的z位置就不建了x,z 之间的边,因为之后y,z会建边,x,z可以通过 xy,yz 来连接。

所以呢,这样子建边就少了很多条边,

这个之后还有一次很重要的操作,就是每一个拆点之间要建一条 流量为inf  费用 为 0 的边。

这个是为了保持图的连通性,比如k点,k点已经被用过一次了,但是k点之前有一个点还没有用,它可以通过k点与另外一个点相连,

但是k点已经用过了,就不可以再跑了,但是这样是不对的,所以为了保持这个图的连通性,保证答案的正确性,这个k点与它的拆点之间还要连一条边。

这条边容量应该是inf,因为你不能保证k点之前还有多少个点通过k点和k点之后的点连在一起。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <queue>
#include <vector>
#define inf 0x3f3f3f3f
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5 + 10;
typedef long long ll;
struct edge {
    int u, v, c, f, cost;
    edge(int u, int v, int c, int f, int cost) :u(u), v(v), c(c), f(f), cost(cost) {}
};
vector<edge>e;
vector<int>G[maxn];
int a[maxn];//找增广路每个点的水流量
int p[maxn];//每次找增广路反向记录路径
int d[maxn];//SPFA算法的最短路
int inq[maxn];//SPFA算法是否在队列中
void init(int n) {
    for (int i = 0; i <= n; i++)G[i].clear();
    e.clear();
}
void addedge(int u, int v, int c, int cost) {
    e.push_back(edge(u, v, c, 0, cost));
    e.push_back(edge(v, u, 0, 0, -cost));
    int m = e.size();
    G[u].push_back(m - 2);
    G[v].push_back(m - 1);
}
bool bellman(int s, int t, int& flow, long long & cost) {
    memset(d, 0xef, sizeof(d));
    memset(inq, 0, sizeof(inq));
    d[s] = 0; inq[s] = 1;//源点s的距离设为0,标记入队
    p[s] = 0; a[s] = INF;//源点流量为INF(和之前的最大流算法是一样的)

    queue<int>q;//Bellman算法和增广路算法同步进行,沿着最短路拓展增广路,得出的解一定是最小费用最大流
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inq[u] = 0;//入队列标记删除
        for (int i = 0; i < G[u].size(); i++) {
            edge & now = e[G[u][i]];
            int v = now.v;
            if (now.c > now.f && d[v] < d[u] + now.cost)
                //now.c > now.f表示这条路还未流满(和最大流一样)
                //d[v] > d[u] + e.cost Bellman 算法中边的松弛
            {
                d[v] = d[u] + now.cost;//Bellman 算法边的松弛
                p[v] = G[u][i];//反向记录边的编号
                a[v] = min(a[u], now.c - now.f);//到达v点的水量取决于边剩余的容量和u点的水量
                if (!inq[v]) { q.push(v); inq[v] = 1; }//Bellman 算法入队
            }
        }
    }
    if (d[t] < 0)return false;//找不到增广路
    flow += a[t];//最大流的值,此函数引用flow这个值,最后可以直接求出flow
    cost += (long long)d[t] * (long long)a[t];//距离乘上到达汇点的流量就是费用
    for (int u = t; u != s; u = e[p[u]].u)//逆向存边
    {
        e[p[u]].f += a[t];//正向边加上流量
        e[p[u] ^ 1].f -= a[t];//反向边减去流量 (和增广路算法一样)
    }
    return true;
}
int MincostMaxflow(int s, int t, long long & cost) {
    cost = 0;
    int flow = 0;
    while (bellman(s, t, flow, cost));//由于Bellman函数用的是引用,所以只要一直调用就可以求出flow和cost
    return flow;//返回最大流,cost引用可以直接返回最小费用
}
int f[maxn], dp[maxn];
int main() {
    int w;
    scanf("%d", &w);
    while (w--) {
        int n, k;
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; i++) scanf("%d", &f[i]);
        int s1 = 0, s2 = 2 * n + 1, t1 = 2 * n + 2, t2 = 2 * n + 3;
        init(t2);
        addedge(s1, s2, k, 0); addedge(t1, t2, k, 0);
        for (int i = 1; i <= n; i++) {
            addedge(i, i + n, 1, f[i]);
            addedge(i, i + n, inf, 0);
            addedge(s2, i, 1, 0);
            addedge(i + n, t1, 1, 0);
        }
        for (int i = 1; i <= n; i++) {
            int x = inf;
            for (int j = i + 1; j <= n; j++) {
                if (f[i] <= f[j] && f[j] < x) addedge(i + n, j, 1, 0), x = f[j];
            }
        }
        ll cost = 0;
        MincostMaxflow(s1, t2, cost);
        printf("%lld
", cost);
    }
    return 0;
}
费用流
原文地址:https://www.cnblogs.com/EchoZQN/p/11268178.html