K Subsequence(HDU-6611)

Problem Description

Master QWsin is dating with Sindar. And now they are in a restaurant, the restaurant has n dishes in order. For each dish, it has a delicious value ai. However, they can only order k times. QWsin and Sindar have a special ordering method, because they believe that by this way they can get maximum happiness value.

Specifically, for each order, they will choose a subsequence of dishes and in this subsequence, when i<j, the subsequence must satisfies ai≤aj. After a round, they will get the sum of the subsequence and they can't choose these dishes again. 

Now, master QWsin wants to know the maximum happiness value they can get but he thinks it's too easy, so he gives the problem to you. Can you answer his question?

Input

 

There are multiple test cases. The first line of the input contains an integer T, indicating the number of test cases. For each test case:

First line contains two positive integers n and k which are separated by spaces.

Second line contains n positive integer a1,a2,a3...an represent the delicious value of each dish.

1≤T≤5
1≤n≤2000
1≤k≤10
1≤ai≤1e5

Output

Only an integer represent the maximum happiness value they can get.

Sample Input

1

9 2
5 3 2 1 4 2 1 4 6

Sample Output

22

题意:t 组样例,每组一个长度为 n 的序列,现在要将这 n 个序列分为 k 个不相交非下降子序列,使得这 k 个子序列的总和最大

思路:

本题实质是要找 k 条路,使得这 k 条路之间两两不相交,而且每条路上的点的值要保证是一个非下降的序列,然后要使得这 k 条路所有的点的和最大

利用网络流可以来解决路径不相交问题,由于要求总和最大,因此利用费用流,并在建边时将价值设为负值,使得选了之后可以得到相应的费用

对于给出的 n 个点,我们将每个点 i 拆分为 ia 与 ib 两个点,之间建立一条容量为 1,费用为 -a[i] 的路,这样可以保证每个点只被选择一次,选了后可以得到 a[i] 的费用

对于 i、j 两点,若 i<j 且 a[i]<=a[j],就建一条从 ib 到 ia 容量为 1,费用为 0 的边,以保证经过的路是一个非下降的序列

最后再建立超级源点与超级汇点,从源点向 ia 建立一条容量为 1,费用为 0 的边,再从 ib 向汇点建立一条容量为 1 费用为 0 的边

建完图后一般跑 MCMF 即可,但本题卡 SPFA,会超时,需要利用基于 Dijkstra 的费用流来做

由于本题在建边时将价值设为负值,因此在跑完费用流得到最后的价值 cost 需要进行取负

Source Program

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<unordered_map>
#include<bitset>
#define PI acos(-1.0)
#define INF 0x3f3f3f3f
#define LL long long
#define Pair pair<int,int>
LL quickPow(LL a,LL b){ LL res=1; while(b){if(b&1)res*=a; a*=a; b>>=1;} return res; }
LL multMod(LL a,LL b,LL mod){ a%=mod; b%=mod; LL res=0; while(b){if(b&1)res=(res+a)%mod; a=(a<<=1)%mod; b>>=1; } return res%mod;}
LL quickPowMod(LL a, LL b,LL mod){ LL res=1,k=a; while(b){if((b&1))res=multMod(res,k,mod)%mod; k=multMod(k,k,mod)%mod; b>>=1;} return res%mod;}
LL getInv(LL a,LL mod){ return quickPowMod(a,mod-2,mod); }
LL GCD(LL x,LL y){ return !y?x:GCD(y,x%y); }
LL LCM(LL x,LL y){ return x/GCD(x,y)*y; }
const double EPS = 1E-10;
const int MOD = 998244353;
const int N = 20000+5;
const int dx[] = {-1,1,0,0,1,-1,1,1};
const int dy[] = {0,0,-1,1,-1,1,-1,1};
using namespace std;

struct Edge {
    int to;
    int cap, dis; //容量、费用
    int rev;      //(u,v)的反向弧中,v在u的位置
    Edge() {}
    Edge(int to, int cap, int dis, int rev): to(to), cap(cap), dis(dis), rev(rev) {}
};
vector<Edge> G[N];
struct Pre {  //记录前驱
    int node; //前驱结点
    int edge; //对应的边
} pre[N];
int h[N];   //势能函数
int dis[N]; //费用
void addEdge(int x, int y, int cap, int dis) {
    G[x].push_back(Edge(y, cap, dis, (int)G[y].size()));      //正向边
    G[y].push_back(Edge(x, 0, -dis, (int)(G[x].size() - 1))); //反向边
}
bool Dijkstra(int S, int T) {
    memset(dis, INF, sizeof(dis));
    dis[S] = 0;

    priority_queue<Pair, vector<Pair>, greater<Pair>> Q;
    Q.push(Pair(dis[S], S));
    while (!Q.empty()) {
        Pair now = Q.top();
        Q.pop();

        int u = now.second;
        if (dis[u] < now.first)
            continue;

        for (int i = 0; i < G[u].size(); i++) {
            int v = G[u][i].to;
            int cap = G[u][i].cap;
            int w = G[u][i].dis;
            if (cap && dis[v] > w + dis[u] + h[u] - h[v]) {
                dis[v] = w + dis[u] + h[u] - h[v]; //进行松弛
                pre[v].node = u;                   //记录前驱点
                pre[v].edge = i;                   //记录前驱边
                Q.push(Pair(dis[v], v));
            }
        }
    }

    if (dis[T] == INF)
        return false;
    else {
        for (int i = 0; i <= T + 1; i++) //对于势能函数,每次加上当前轮的dis
            h[i] += dis[i];
        return true;
    }
}
void maxFlow(int S, int T, int &flow, int &cost) {
    memset(h, 0, sizeof(h));
    memset(pre, 0, sizeof(pre));

    int newFlow = 0;                 //增广流量
    while (flow && Dijkstra(S, T)) { //当无法增广时,即找到答案
        int minn = INF;
        for (int i = T; i != S; i = pre[i].node) {
            int node = pre[i].node;
            int edge = pre[i].edge;
            minn = min(minn, G[node][edge].cap);
        }

        flow -= minn;        //原流量
        newFlow += minn;     //增广流量
        cost += h[T] * minn; //增广流花销

        for (int i = T; i != S; i = pre[i].node) {
            int node = pre[i].node;
            int edge = pre[i].edge;
            int rev = G[node][edge].rev;
            G[node][edge].cap -= minn;
            G[i][rev].cap += minn;
        }
    }
}
int a[N];
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        for (int i = 0; i < N; i++)
            G[i].clear();

        int n,k;
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);

        int minn = 0;
        int S = 1;//超级源点
        int T = 2 * n + 2;//超级汇点
        for (int i = 1; i <= n; i++) {
            addEdge(i, i + n, 1, -a[i]);//拆点,点ia到ib建边
            addEdge(S, i, 1, 0);//超级源S到点ia建边
            addEdge(i + n, T, 1, 0);//ib到超级汇T建边

            for (int j = 1; j < i; j++)//满足非下降序列特征的i、j两点建边
                if (a[j] <= a[i])
                    addEdge(j + n, i, 1, 0);
        }

        int cost=0;
        int flow =k; //一般设为INF,由于本题要找k个不同的路,且每条路的容量都是1,因此最大流量为k
        maxFlow(S,T, flow, cost);
        cost=-cost;//由于建边时设置的价值为负,因此最后要记得取负
        printf("%d\n", cost);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值