题意
给定一个 $n$ 个整数的数列,从中至多选取 $k$ 个上升子序列(一个元素最多被选一次),使得选取的元素和最大。
分析
考虑这个问题和经典网络流问题“最长不下降子序列”相似,我们考虑对这个建图并用网络流解决。因为求得费用和,则使用费用流做法。
具体建图见代码,主要考虑拆点和建立超级源点和超级汇点。
(然后SPFA版的会超时,换成Dijkstra版的
#includeusing namespace std; #define il inline typedef long long ll; const int INF = 0x3f3f3f3f; const int maxn = 4000 + 10; int n, k, a[maxn]; struct edge { int to, capacity, cost, rev; edge() {} edge(int to, int _capacity, int _cost, int _rev) :to(to), capacity(_capacity), cost(_cost), rev(_rev) {} }; struct Min_Cost_Max_Flow { int V, H[maxn + 5], dis[maxn + 5], PreV[maxn + 5], PreE[maxn + 5]; vector G[maxn + 5]; //调用前初始化 void Init(int n) { V = n; for (int i = 0; i <= V; ++i)G[i].clear(); } //加边 void Add_Edge(int from, int to, int cap, int cost) { G[from].push_back(edge(to, cap, cost, G[to].size())); G[to].push_back(edge(from, 0, -cost, G[from].size() - 1)); } //flow是自己传进去的变量,就是最后的最大流,返回的是最小费用 int Min_cost_max_flow(int s, int t, int f, int& flow) { int res = 0; fill(H, H + 1 + V, 0); while (f) { priority_queue int, int>, vector int, int>>, greater int, int>> > q; fill(dis, dis + 1 + V, INF); dis[s] = 0; q.push(pair<int, int>(0, s)); while (!q.empty()) { pair<int, int> now = q.top(); q.pop(); int v = now.second; if (dis[v] < now.first)continue; for (int i = 0; i < G[v].size(); ++i) { edge& e = G[v][i]; if (e.capacity > 0 && dis[e.to] > dis[v] + e.cost + H[v] - H[e.to]) { dis[e.to] = dis[v] + e.cost + H[v] - H[e.to]; PreV[e.to] = v; PreE[e.to] = i; q.push(pair<int, int>(dis[e.to], e.to)); } } } if (dis[t] == INF)break; for (int i = 0; i <= V; ++i)H[i] += dis[i]; int d = f; for (int v = t; v != s; v = PreV[v])d = min(d, G[PreV[v]][PreE[v]].capacity); f -= d; flow += d; res += d*H[t]; for (int v = t; v != s; v = PreV[v]) { edge& e = G[PreV[v]][PreE[v]]; e.capacity -= d; G[v][e.rev].capacity += d; } } return res; } int Max_cost_max_flow(int s, int t, int f, int& flow) { int res = 0; fill(H, H + 1 + V, 0); while (f) { priority_queue int, int>> q; fill(dis, dis + 1 + V, -INF); dis[s] = 0; q.push(pair<int, int>(0, s)); while (!q.empty()) { pair<int, int> now = q.top(); q.pop(); int v = now.second; if (dis[v] > now.first)continue; for (int i = 0; i < G[v].size(); ++i) { edge& e = G[v][i]; if (e.capacity > 0 && dis[e.to] < dis[v] + e.cost + H[v] - H[e.to]) { dis[e.to] = dis[v] + e.cost + H[v] - H[e.to]; PreV[e.to] = v; PreE[e.to] = i; q.push(pair<int, int>(dis[e.to], e.to)); } } } if (dis[t] == -INF)break; for (int i = 0; i <= V; ++i)H[i] += dis[i]; int d = f; for (int v = t; v != s; v = PreV[v])d = min(d, G[PreV[v]][PreE[v]].capacity); f -= d; flow += d; res += d*H[t]; for (int v = t; v != s; v = PreV[v]) { edge& e = G[PreV[v]][PreE[v]]; e.capacity -= d; G[v][e.rev].capacity += d; } } return res; } }mcmf; void solve() { mcmf.Init(2*n+3); for(int i = 1;i <= n;i++) { mcmf.Add_Edge(i, i+n, 1, -a[i]); for(int j = i+1;j <= n;j++) { if(a[j] >= a[i]) { mcmf.Add_Edge(i+n, j, 1, 0); //右边每一个大于的都要连边 //break; } } } mcmf.Add_Edge(2*n+1, 2*n+2, k, 0); for(int i = 1;i <= n;i++) mcmf.Add_Edge(2*n+2, i, 1, 0); //建立超级源点2n+1 for(int i = 1;i <= n;i++) mcmf.Add_Edge(i+n, 2*n+3, 1, 0); //建立超级汇点2n+2 int flow = 0; int ans = mcmf.Min_cost_max_flow(2*n+1, 2*n+3, INF, flow); printf("%d\n", -ans); } int main() { // freopen("multi.in", "r", stdin); // freopen("out.txt", "w", stdout); int T; scanf("%d", &T); while(T--) { scanf("%d%d", &n, &k); for(int i = 1;i <= n;i++) scanf("%d", &a[i]); solve(); } }