Libre 6005 「网络流 24 题」最长递增子序列 / Luogu 2766 最长递增子序列问题(网络流,最大流)...

Libre 6005 「网络流 24 题」最长递增子序列 / Luogu 2766 最长递增子序列问题(网络流,最大流)

Description

问题描述:

给定正整数序列x1,...,xn 。

(1)计算其最长递增子序列的长度s。

(2)计算从给定的序列中最多可取出多少个长度为s的递增子序列。

(3)如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长度为s的递增子序列。

编程任务:

设计有效算法完成(1)(2)(3)提出的计算任务。

Input

第1 行有1个正整数n,表示给定序列的长度。接下来的1 行有n个正整数n:x1, ..., xn。

Output

第1 行是最长递增子序列的长度s。第2行是可取出的长度为s 的递增子序列个数。第3行是允许在取出的序列中多次使用x1和xn时可取出的长度为s 的递增子序列个数。

Sample Input

4
3 6 2 5

Sample Output

2
2
3

Http

Libre:https://loj.ac/problem/6005
Luogu:https://www.luogu.org/problem/show?pid=2766

Source

网络流,最大流

解决思路

看清题目,不是最长递增子序列是最长不下降子序列。
这道题目首先运用动态规划的方式求出最长不下降子序列,这也是第一问的内容。注意,本题不能使用单调队列的方式,因为要求出到每一个数的最长不下降子序列长度(后面记为F),这在后两问中要用。
那么如何求解第二问呢?
我们把每一个数拆成两个点入点和出点,在每一个数的入点和出点之间连容量为1的边,同时设置一个源点一个汇点。从前往后扫描每一个数,若发现第i个数的F[i]==最长不下降子序列长度,则在源点与i的出点之间连一条容量为1的边。若F[i]==1,则在其出点与汇点之间连一条容量为1的边。并且,对于任何数i,扫描其前面的每一个数j,若F[i]==F[j]+1且第j位的数<=第i位的数,则在i的出点与j的入点之间连一条容量为1的边。
这样建图,有点类似于分层图的思想,从最高层的F为最长不下降子序列长度,往下每一层长度减一,直到最底下一层长度为1。这样我们跑一边最短路就可以了。
至于第三问,我们只要在重新建图的时候把1到汇点,1的入点到出点,n的入点到出点,源点到n(如果存在的话)这几条边设置为无穷大即可。
但要注意,第三问要特判一下递减的情况,因为这样最长不下降子序列长度为1,跑最大流会出现无穷大的流的情况。
另:这里用Dinic求解最大流,具体请移步我的这篇文章

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxN=2000;
const int maxM=maxN*maxN*4;
const int inf=147483647;

class Edge
{
public:
    int u,v,flow;
};

int n;
int cnt=-1;
int F[maxN];
int Arr[maxN];
int Head[maxN];
int Next[maxM];
Edge E[maxM];
int depth[maxN];
int cur[maxN];
int Q[maxM];

void Add_Edge(int u,int v,int flow);
bool bfs();
int dfs(int u,int flow);

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&Arr[i]);
    for (int i=1;i<=n;i++)//动态规划求出最长不下降子序列
    {
        F[i]=1;
        for (int j=1;j<i;j++)
            if (Arr[j]<=Arr[i])
                F[i]=max(F[i],F[j]+1);
    }
    int maxlength=1;
    for (int i=1;i<=n;i++)
        maxlength=max(maxlength,F[i]);//得出第一问答案
    printf("%d\n",maxlength);
    memset(Head,-1,sizeof(Head));
    for (int i=1;i<=n;i++)//构造第二问的图
    {
        Add_Edge(i,i+n,1)//连接入点和出点
        if (F[i]==maxlength)//若与最长长度相同,则连接源点
            Add_Edge(0,i,1);
        if (F[i]==1)//若为最小长度1,则连接汇点
            Add_Edge(i+n,n*2+1,1);
        for (int j=1;j<i;j++)
            if ((F[j]==F[i]-1)&&(Arr[j]<=Arr[i]))//向前统计能连的
                Add_Edge(i+n,j,1);
    }
    int Ans=0;//求解最大流
    while (bfs())
    {
        for (int i=0;i<=2*n+1;i++)
            cur[i]=Head[i];
        while (int di=dfs(0,inf))
            Ans+=di;
    }
    cout<<Ans<<endl;
    memset(Head,-1,sizeof(Head));
    cnt=-1;
    for (int i=1;i<=n;i++)//构造第三问的图
    {
        int nowflow=1;
        if ((i==1)||(i==n))//1和n的流量为无穷大
            nowflow=inf;
        if (maxlength==1)//注意这里特判递减序列的情况
            Add_Edge(i,i+n,1);
        else
            Add_Edge(i,i+n,inf);
        if (F[i]==maxlength)
            Add_Edge(0,i,nowflow);
        if (F[i]==1)
            Add_Edge(i+n,n*2+1,nowflow);
        for (int j=1;j<i;j++)
            if ((F[j]==F[i]-1)&&(Arr[j]<=Arr[i]))
                Add_Edge(i+n,j,1);
    }
    Ans=0;//求解最大流
    while (bfs())
    {
        for (int i=0;i<=2*n+1;i++)
            cur[i]=Head[i];
        while (int di=dfs(0,inf))
            Ans+=di;
    }
    cout<<Ans<<endl;
    return 0;
}

void Add_Edge(int u,int v,int flow)
{
    cnt++;
    Next[cnt]=Head[u];
    Head[u]=cnt;
    E[cnt].u=u;
    E[cnt].v=v;
    E[cnt].flow=flow;

    cnt++;
    Next[cnt]=Head[v];
    Head[v]=cnt;
    E[cnt].v=u;
    E[cnt].u=v;
    E[cnt].flow=0;
}

bool bfs()
{
    memset(depth,-1,sizeof(depth));
    int h=1,t=0;
    Q[1]=0;
    depth[0]=1;
    do
    {
        t++;
        int u=Q[t];
        //cout<<u<<endl;
        for (int i=Head[u];i!=-1;i=Next[i])
        {
            int v=E[i].v;
            if ((depth[v]==-1)&&(E[i].flow>0))
            {
                depth[v]=depth[u]+1;
                h++;
                Q[h]=v;
            }
        }
    }
    while (t!=h);
    if (depth[n*2+1]==-1)
        return 0;
    return 1;
}

int dfs(int u,int flow)
{
    if (u==n*2+1)
        return flow;
    for (int &i=cur[u];i!=-1;i=Next[i])
    {
        int v=E[i].v;
        if ((depth[v]==depth[u]+1)&&(E[i].flow>0))
        {
            int di=dfs(v,min(flow,E[i].flow));
            if (di>0)
            {
                E[i].flow-=di;
                E[i^1].flow+=di;
                return di;
            }
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/SYCstudio/p/7280857.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LibreOffice 是一个自由开源的办公软件套件,其中包含了Writer这个功能强大的文字处理器。Writer 是 LibreOffice 中的一个组件,它可以用来编辑和创建 Word 文档。 使用 LibreOffice 的 Writer,用户可以打开和编辑 Word 文档,并且可以保存为 Word 格式。这意味着你可以在没有 Microsoft Office 的情况下,使用 LibreOffice 来编辑并保存 Word 文档。通过在线版的 LibreOffice,你甚至可以在没有安装任何软件的情况下,只需一个浏览器和互联网连接,就能够访问和编辑 Word 文档。 LibreOffice 的在线编辑功能十分方便。只需在浏览器中打开 LibreOffice Online 或通过支持 LibreOffice Online 的服务提供商进行访问,然后登录并上传 Word 文档。之后,你就可以像在本地编辑器中一样进行编辑,添加、删除或修改内容,设置格式,插入图片、表格、链接等。在编辑过程中,你还可以使用类似 Microsoft Word 的工具栏和菜单进行操作。 完成编辑后,你可以选择保存并下载这个文档,以供后续使用。与保存为 LibreOffice 的原生格式相比,保存为 Word 格式可以保留文档的格式和布局,使其能够与其他使用 Microsoft Office 或其他软件的用户无缝交和共享。 总之,LibreOffice 的在线编辑功能允许用户通过浏览器进行编辑和保存 Word 文档,无需安装 Microsoft Office。它为用户提供了一个轻量级、灵活且易于使用的编辑工具,使用户能够随时随地访问和编辑 Word 文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值