[lnsyoj2612/luoguP2766] 最长不下降子序列问题

题意

给定正整数序列 x1,xn,求

  1. 最长不下降子序列的长度 s
  2. 如果每个元素只允许使用一次,从给定的序列中最多可取出多少个长度为 s 的不下降子序列。
  3. 如果允许在取出的序列中多次使用 x1xn(其他元素仍然只允许使用一次),则从给定序列中最多可取出多少个不同的长度为 s 的不下降子序列。

sol

1 问是 LIS 板子题,记最终的结果为 mxn
2 问中,如果将序列中的元素作为点,将可以 dp 数组的转移作为边,就可以建出一个图。问题等价于求这个图中,只能覆盖每个点 1 次,给定所有源点(满足 dps=1 的所有点 s)和汇点(满足 dpt=mxn 的所有点 t),最多能覆盖多少个长度为 s1 的路径。
由于容量的限制在于点而非边,这里可以使用拆点的技巧,即将一个点在网络中拆为两个点,在两点之间建一条容量为原来的点容量的弧。在这个网络上求网络流即可。
需要注意的是,由于可能存在多个起点和终点,因此需要建立超级源点和超级汇点。并向每一个可行的源汇点建弧,容量均为 1
3 问和第 2 问等价,但是由于点 1 和点 n 无容量限制,因此需要对与超级源汇点及点内部的容量进行调整。
注意特判 n=1

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int K = 505, N = K * 2, M = N * N, INF = 0x3f3f3f3f;

int f[K];
int nn;
int a[K];
int mxn;
int h[N], e[M], cap[M], ne[M], idx;
int d[N], cur[N];
int n, S, T;

int dp(){
    for (int i = 1; i <= nn; i ++ ) {
        f[i] = 1;
        for (int j = 1; j < i; j ++ )
            if (a[j] <= a[i])
                f[i] = max(f[i], f[j] + 1);
    }

    int res = 0;
    for (int i = 1; i <= nn; i ++ ) res = max(res, f[i]);
    return res;
}

bool bfs() {
    memset(d, -1, sizeof d);
    queue<int> q;
    d[S] = 0, cur[S] = h[S], q.push(S);
    while (!q.empty()) {
        int t = q.front(); q.pop();
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (d[j] == -1 && cap[i]) {
                d[j] = d[t] + 1;
                cur[j] = h[j];
                if (j == T) return true;
                q.push(j);
            }
        }
    }
    return false;
}

int find(int u, int limit){
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
        int j = e[i];
        cur[u] = i;
        if (d[j] == d[u] + 1 && cap[i]) {
            int t = find(j, min(cap[i], limit - flow));
            if (!t) d[j] = -1;
            cap[i] -= t, cap[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

int dinic(){
    int r = 0, flow;
    while (bfs()) while (flow = find(S, INF)) r += flow;
    return r;
}

void add(int a, int b, int c){
    e[idx] = b, cap[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, cap[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}

void build() {
    memset(h, -1, sizeof h);
    n = nn * 2 + 2; S = n - 1, T = n;
    for (int i = 1; i <= nn; i ++ ) add(i * 2 - 1, i * 2, 1);
    for (int i = 1; i <= nn; i ++ ) if (f[i] == 1) add(S, i * 2 - 1, 1);
    for (int i = 1; i <= nn; i ++ ) if (f[i] == mxn) add(i * 2, T, 1);
    for (int i = 1; i < nn; i ++ )
        for (int j = i + 1; j <= nn; j ++ )
            if (a[i] <= a[j] && f[i] + 1 == f[j]) add(i * 2, j * 2 - 1, INF);
}

void rebuild(){
    memset(h, -1, sizeof h);
    idx = 0;
    n = nn * 2 + 2; S = n - 1, T = n;
    add(1, 2, INF), add(nn * 2 - 1, nn * 2, INF);
    for (int i = 2; i < nn; i ++ ) add(i * 2 - 1, i * 2, 1);
    for (int i = 1; i <= nn; i ++ ) 
        if (f[i] == 1) {
            if (i == 1) add(S, i * 2 - 1, INF);
            else add(S, i * 2 - 1, 1);
        }
    for (int i = 1; i <= nn; i ++ ) 
        if (f[i] == mxn) {
            if (i == nn) add(i * 2, T, INF);
            else add(i * 2, T, 1);
        }
    for (int i = 1; i < nn; i ++ )
        for (int j = i + 1; j <= nn; j ++ )
            if (a[i] <= a[j] && f[i] + 1 == f[j]) add(i * 2, j * 2 - 1, INF);
}

int main(){
    scanf("%d", &nn);
    for (int i = 1; i <= nn; i ++ ) scanf("%d", &a[i]);
    mxn = dp();
    printf("%d\n", mxn);

    build();
    printf("%d\n", dinic());

    if (nn == 1) return puts("1"), 0;
    rebuild();
    printf("%d\n", dinic());
}

蒟蒻犯的若至错误

  • 变量名混淆
posted @   是一只小蒟蒻呀  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示