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; }