【网络流 24 题】最长递增子序列(最多不相交路径)

链接【网络流 24 题】最长递增子序列

题目描述

给定正整数序列 ,以下递增子序列均为非严格递增。

  1. 计算其最长递增子序列的长度s ss
  2. 计算从给定的序列中最多可取出多少个长度为s ss的递增子序列。
  3. 如果允许在取出的序列中多次使用x 1 x_1x1x n x_nxn,则从给定序列中最多可取出多少个长度为s ss的递增子序列。

输入格式

文件第1 11行有1 11个正整数 ,表示给定序列的长度。接下来的1 11行有n nn个正整数x 1 x_1x1~x n x_nxn

输出格式

1 11行是最长递增子序列的长度 。第2 22行是可取出的长度为s ss的递增子序列个数。第3 33行是允许在取出的序列中多次使用x 1 x_1x1x 2 x_2x2时可取出的长度为s ss的递增子序列个数。

样例输入

4
3 6 2 5

样例输出

2
2
3



分析:

在建模前,需要预处理得到d p [ i ] dp[i]dp[i]:表示以x i x_ixi结尾可以得到的最长递增子序列长度,同时可得第一问的答案——最长递增子序列的长度s ss


对每一个点进行拆点,如点u uu拆分为u → u ′ u\rarr u'uu(入点为u uu,出点为u ′ u'u),容量为1,表示仅可选一次;

对于d p [ i ] = 1 dp[i]=1dp[i]=1的点,说明可以作为最长递增子序列的起点,连边s → i s\rarr isi,容量为∞ \infin
对于d p [ i ] = s dp[i]=sdp[i]=s的点,说明可以作为最长递增子序列的终点,连边i ′ → t i'\rarr tit,容量为∞ \infin

而对于点之间,若d p [ i ] + 1 = d p [ j ] dp[i]+1=dp[j]dp[i]+1=dp[j],说明x j x_jxj可以接在x i x_ixi之后,连边i ′ → j i'\rarr jij,容量为1。

跑最大流,即为第二问答案。


对于第三问,只需将点1和点n的拆点容量改为∞ \infin即可,但要注意特判s = 1 s=1s=1的情况。



代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int maxn = 1e6 + 10;
int n, s = 0, t = maxn - 1;
int a[maxn];
int head[maxn], cnt;
struct edge {
    int w;     //边的流量(残量)
    int to;    //该边通向的结点v
    int next;  //点u邻接表的下一条边
} e[maxn];
void add_edge(int u, int v, int w)  //添加一条u->v,最大容量为w的边
{
    //建立正向边
    e[cnt].w = w;
    e[cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
    cnt++;
    //建立反向边
    e[cnt].w = 0;  //有些图是需要建立双向边(例如求最小割时),则反向边的初始残量不为0
    e[cnt].to = u;
    e[cnt].next = head[v];
    head[v] = cnt;
    cnt++;
}
int dis[maxn];  // dis数组记录层次
bool bfs()      //利用BFS建立分成图,从而可以多次DFS增广
{
    memset(dis, -1, sizeof(dis));  //初始化dis数组
    queue<int> q;
    q.push(s);
    dis[s] = 0;  //源点层次为0
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = head[u]; i != -1; i = e[i].next) {
            int v = e[i].to;
            if (e[i].w > 0 && dis[v] == -1)  //可达&&未分层
            {
                dis[v] = dis[u] + 1;  //分层
                if (v == t)           //若到达汇点,则分层结束,返回true
                    return true;
                q.push(v);
            }
        }
    }
    return false;  //运行到此处,说明汇点已不可达,返回false
}
int cur[maxn];  //弧优化:cur数组用于记录上一次DFS增广时u已经增广到第几条边,从而优化时间
int dfs(int u, int flow)  // flow代表流入u点的最大流量
{
    if (u == t)
        return flow;                                 //到达汇点,直接返回flow
    for (int &i = cur[u]; i != -1; i = e[i].next) {  //注意i前面用&引用,这样就可以直接改变cur[u]
        int v = e[i].to;
        if (dis[v] == dis[u] + 1 && e[i].w > 0)  // v为u的下一层&&可达
        {
            int k = dfs(v, min(flow, e[i].w));
            if (k > 0) {
                e[i].w -= k;      //正向边-=k
                e[i ^ 1].w += k;  //反向边+=k
                return k;
            }
        }
    }
    return 0;  //无法继续增广,返回0
}
int dinic() {
    int ans = 0;   //记录总流量
    while (bfs())  //分层
    {
        for (int i = 0; i < maxn; i++)  //初始化cur数组,即将head数组赋给cur数组
            cur[i] = head[i];
        while (int k = dfs(s, INF))  //增广
            ans += k;
    }
    return ans;
}
int dp[1010], max_s;
void init() {
    memset(head, -1, sizeof(head));
    cnt = 0;
}
int main() {
    init();
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) {
        dp[i] = 1;
        for (int j = 1; j < i; j++) {
            if (a[j] <= a[i])
                dp[i] = max(dp[i], dp[j] + 1);
        }
        max_s = max(max_s, dp[i]);
    }
    printf("%d\n", max_s);
    for (int i = 1; i <= n; i++) {
        add_edge(i, n + i, 1);
        if (dp[i] == 1)
            add_edge(s, i, INF);
        if (dp[i] == max_s)
            add_edge(n + i, t, INF);
        for (int j = 1; j < i; j++) {
            if (a[j] <= a[i] && dp[j] + 1 == dp[i])
                add_edge(n + j, i, 1);
        }
    }
    printf("%d\n", dinic());
    init();
    for (int i = 1; i <= n; i++) {
        if (i == 1 || i == n)
            add_edge(i, n + i, INF);
        else
            add_edge(i, n + i, 1);
        if (dp[i] == 1)
            add_edge(s, i, INF);
        if (dp[i] == max_s)
            add_edge(n + i, t, INF);
        for (int j = 1; j < i; j++) {
            if (a[j] <= a[i] && dp[j] + 1 == dp[i])
                add_edge(n + j, i, 1);
        }
    }
    if (max_s == 1)
        printf("%d\n", n);  //特判
    else
        printf("%d\n", dinic());
    return 0;
}

版权声明:本文为Ratina原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。