HDU 3998 Sequence (最长递增子序列+最大流SAP,拆点法)经典

Sequence

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

Total Submission(s): 1666    Accepted Submission(s): 614

Problem Description

There is a sequence X (i.e. x[1], x[2], ..., x[n]). We define increasing subsequence of X

as x[i1], x[i2],...,x[ik], which satisfies follow conditions:

1) x[i1] < x[i2],...,<x[ik];

2) 1<=i1 < i2,...,<ik<=n

As an excellent program designer, you must know how to find the maximum length of the

increasing sequense, which is defined as s. Now, the next question is how many increasing

subsequence with s-length can you find out from the sequence X.

For example, in one case, if s = 3, and you can find out 2 such subsequence A and B from X.

1) A = a1, a2, a3. B = b1, b2, b3.

2) Each ai or bj(i,j = 1,2,3) can only be chose once at most.

Now, the question is:

1) Find the maximum length of increasing subsequence of X(i.e. s).

2) Find the number of increasing subsequence with s-length under conditions described (i.e. num).

Input

The input file have many cases. Each case will give a integer number n.The next line will

have n numbers.

Output

The output have two line. The first line is s and second line is num.

Sample Input

4
3 6 2 5

Sample Output

2
2

Source

2011 Multi-University Training Contest 16 - Host by TJU

此题有二解:

方一:dp+最大流       (我所用的方法)

方二:思路:可以用n*log(n)的做法求出最长上升子序列,然后删除原数组中的这些数,再求最长上升子序列(如果长度减小,则直接退出)。

题意:给一个序列X(x[1] , x[2] , ....x[n]) , 个数为n 。求最长递增子序列长度,及 最长递增子序列的个数。要求:1):  x[i1] < x[i2],...,<x[ik];( 1<=i1 < i2,...,<ik<=n )  2):每个数只能使用一次。

解题:对于第一问:最长递增子序列的长度ans   很好求。现在对于求第二问,我们先记下求最长递增子序列时每个数属于哪些位置(即长度)。先来做个假设:如果一个数num在记录的第2个位置,且以num为最小值,能找到一个长度为ans的最长递增子序列,那么 我们可以再加一个数(第1个位置的数)到此序列中,此时最长递增子序列长度为 ans+1,  因ans < ans+1,所以与先前求出的最长递增子序列长度ans矛盾。所以假设不成立 。

结论:一个最长递增子序列的起始数一定是从第1个位置中的数开始。这也就是为什么下面用 最大流 来解的最重要的原因。

建图:(1)先每个看成一个点,然后把每个点i 拆成两个点建一条边:i -->(i+n) 边容为 1(拆点后,i 点只进不出,i+n点只出不入,除了自身边)。(2)再把源点s=0与第1个位置的每个点 i 建一条边容量为 1 :s-->i。(3)把每ans位置的每个点与汇点 t = 2*n+1 建一条边容量为1:(i+n)-->t。(4)第len长度的点 u 与第len+1长度的点 v 建一条边容量为1。满足的要求:题意中的 1)和 2)。

#include<stdio.h>
#include<string.h>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define captype int

const int MAXN = 100010;   //点的总数
const int MAXM = 400010;    //边的总数
const int INF = 1<<30;
struct EDG{
    int to,next;
    captype cap,flow;
} edg[MAXM];
int eid,head[MAXN];
int gap[MAXN];  //每种距离(或可认为是高度)点的个数
int dis[MAXN];  //每个点到终点eNode 的最短距离
int cur[MAXN];  //cur[u] 表示从u点出发可流经 cur[u] 号边
int pre[MAXN];

void init(){
    eid=0;
    memset(head,-1,sizeof(head));
}
//有向边 三个参数,无向边4个参数
void addEdg(int u,int v,captype c,captype rc=0){
    edg[eid].to=v; edg[eid].next=head[u];
    edg[eid].cap=c; edg[eid].flow=0; head[u]=eid++;

    edg[eid].to=u; edg[eid].next=head[v];
    edg[eid].cap=rc; edg[eid].flow=0; head[v]=eid++;
}
captype maxFlow_sap(int sNode,int eNode, int n){//n是包括源点和汇点的总点个数,这个一定要注意
    memset(gap,0,sizeof(gap));
    memset(dis,0,sizeof(dis));
    memcpy(cur,head,sizeof(head));
    pre[sNode] = -1;
    gap[0]=n;
    captype ans=0;  //最大流
    int u=sNode;
    while(dis[sNode]<n){   //判断从sNode点有没有流向下一个相邻的点
        if(u==eNode){   //找到一条可增流的路
            captype Min=INF ;
            int inser;
            for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to])    //从这条可增流的路找到最多可增的流量Min
            if(Min>edg[i].cap-edg[i].flow){
                Min=edg[i].cap-edg[i].flow;
                inser=i;
            }
            for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to]){
                edg[i].flow+=Min;
                edg[i^1].flow-=Min;  //可回流的边的流量
            }
            ans+=Min;
            u=edg[inser^1].to;
            continue;
        }
        bool flag = false;  //判断能否从u点出发可往相邻点流
        int v;
        for(int i=cur[u]; i!=-1; i=edg[i].next){
            v=edg[i].to;
            if(edg[i].cap-edg[i].flow>0 && dis[u]==dis[v]+1){
                flag=true;
                cur[u]=pre[v]=i;
                break;
            }
        }
        if(flag){
            u=v;
            continue;
        }
        //如果上面没有找到一个可流的相邻点,则改变出发点u的距离(也可认为是高度)为相邻可流点的最小距离+1
        int Mind= n;
        for(int i=head[u]; i!=-1; i=edg[i].next)
        if(edg[i].cap-edg[i].flow>0 && Mind>dis[edg[i].to]){
            Mind=dis[edg[i].to];
            cur[u]=i;
        }
        gap[dis[u]]--;
        if(gap[dis[u]]==0) return ans;  //当dis[u]这种距离的点没有了,也就不可能从源点出发找到一条增广流路径
                                        //因为汇点到当前点的距离只有一种,那么从源点到汇点必然经过当前点,然而当前点又没能找到可流向的点,那么必然断流
        dis[u]=Mind+1;//如果找到一个可流的相邻点,则距离为相邻点距离+1,如果找不到,则为n+1
        gap[dis[u]]++;
        if(u!=sNode) u=edg[pre[u]^1].to;  //退一条边
    }
    return ans;
}

int binarySearch(int* dp , int num ,int ans){
    int l=1 , r = ans , mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(dp[mid]>=num)
            r = mid-1;
        else
            l=mid+1;
    }
    return l;
}
int main(){
    int n , num[MAXN] , dp[MAXN];
    while(scanf("%d",&n)>0){
        for(int i=1; i<= n; i++)
            scanf("%d",&num[i]),dp[i]= INF;

        int ans=0;  //最长递增子序列的长度
        vector<vector<int> >vct;
        vct = vector<vector<int> >(n,vector<int>(0,0));

        for(int i=1; i<= n; i++){
            int len= binarySearch(dp,num[i],ans);
            vct[len].push_back(i);
            if(dp[len]>num[i]) dp[len]=num[i];
            if(ans<len) ans=len;
        }
        init();
        int s = 0, t = 2*n+1;
        for(int i=1; i<=n; i++) //拆点,自身点看作一条边,每个点只能用一次所以容量为1
            addEdg( i , i+n , 1 );
        for(int i=vct[1].size()-1; i>=0; i--)
            addEdg(s , vct[1][i] , 1);
        for(int i=vct[ans].size()-1; i>=0; i--)
            addEdg(vct[ans][i]+n , t , 1);

        for(int len = 1; len<ans; len++)
        for(int i = vct[len].size()-1 ; i>=0; i--){
            int u=vct[len][i];
            for(int j = vct[len+1].size()-1 ; j>=0; j--){
                int v=vct[len+1][j];
                if(u > v)break;                 //如果v位置在u的位置前方,则不能满足:1<=i1 < i2,...,<ik<=n
                if(num[u]>=num[v]) continue;
                addEdg(u+n , v , 1);
            }
        }

        int k=maxFlow_sap(s , t , t+1); //最长递增子序列的个数,相当寻找(s-->t)路的条数,每个边只能用一次
        printf("%d\n%d\n",ans,k);
    }
}
时间: 2024-10-03 14:03:11

HDU 3998 Sequence (最长递增子序列+最大流SAP,拆点法)经典的相关文章

UVa 10534 Wavio Sequence (最长递增子序列 DP 二分)

Wavio Sequence  Wavio is a sequence of integers. It has some interesting properties. ·  Wavio is of odd length i.e. L = 2*n + 1. ·  The first (n+1) integers of Wavio sequence makes a strictly increasing sequence. ·  The last (n+1) integers of Wavio s

HDU 1069 dp最长递增子序列

B - Monkey and Banana Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status Practice HDU 1069 Appoint description: Description A group of researchers are designing an experiment to test the IQ of a monkey. They wi

HDU 3998 Sequence(经典问题,最长上升子序列)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3998 解题报告:求一个数列的最长上升子序列,并求像这样长的不相交的子序列最多有多少个. 我用的是最简单的方法,就是每次求最长上升子序列,然后每次将求得的子序列从数列里面删掉,然后再对剩下的数列求最长上升子序列,如果求得的子序列的长度比第一次求得的长度小的话,就退出.不过我这题卡了很久,原因就是因为用这种方法求的过程中,用到了很多变量,但是没有注意每一步求最长上升子序列的时候都要进行初始化,哎. Vi

最长公共子序列(LCS)、最长递增子序列(LIS)、最长递增公共子序列(LICS)

最长公共子序列(LCS) [问题] 求两字符序列的最长公共字符子序列 问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj.例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列. 考虑最长公共子序列问题如何分解成

UVa10534Wavio Sequence (最长上升子序列LIS)

Wavio Sequence Input: Standard Input Output: Standard Output Time Limit: 2 Seconds Wavio is a sequence of integers. It has some interesting properties. ·  Wavio is of odd length i.e. L = 2*n + 1. ·  The first (n+1) integers of Wavio sequence makes a

poj之最长递增子序列

题目:POJ 2533   Longest Ordered Subsequence Description A numeric sequence of ai is ordered if a1 < a2 < ... < aN. Let the subsequence of the given numeric sequence (a1, a2, ..., aN) be any sequence (ai1, ai2, ..., aiK), where 1 <= i1 < i2 &l

POJ 2533 Longest Ordered Subsequence【最长递增子序列】【DP思想】

Longest Ordered Subsequence Time Limit : 4000/2000ms (Java/Other)   Memory Limit : 131072/65536K (Java/Other) Total Submission(s) : 6   Accepted Submission(s) : 1 Problem Description A numeric sequence of ai is ordered ifa1 < a2 < ... < aN. Let t

LIS(最长递增子序列)和LCS(最长公共子序列)的总结

最长公共子序列(LCS):O(n^2) 两个for循环让两个字符串按位的匹配:i in range(1, len1) j in range(1, len2) s1[i - 1] == s2[j - 1], dp[i][j] = dp[i - 1][j -1] + 1; s1[i - 1] != s2[j - 1], dp[i][j] = max (dp[i - 1][j], dp[i][j - 1]); 初始化:dp[i][0] = dp[0][j] = 0; 伪代码: dp[maxn1][ma

算法面试题 之 最长递增子序列 LIS

找出最长递增序列 O(NlogN)(不一定连续!) 参考 http://www.felix021.com/blog/read.php?1587%E5%8F%AF%E6%98%AF%E8%BF%9E%E6%95%B0%E7%BB%84%E9%83%BD%E6%B2%A1%E7%BB%99%E5%87%BA%E6%9D%A5 我就是理解了一下他的分析 用更通俗易懂的话来说说题目是这样 d[1..9] = 2 1 5 3 6 4 8 9 7 要求找到最长的递增子序列首先用一个数组b[] 依次的将d里面