hdu 5748(求解最长上升子序列的两种O(nlogn)姿势)

Bellovin

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 707    Accepted Submission(s): 328

Problem Description
Peter has a sequence a1,a2,...,an and he define a function on the sequence -- F(a1,a2,...,an)=(f1,f2,...,fn), where fi is the length of the longest increasing subsequence ending with ai.

Peter would like to find another sequence b1,b2,...,bn in such a manner that F(a1,a2,...,an) equals to F(b1,b2,...,bn). Among all the possible sequences consisting of only positive integers, Peter wants the lexicographically smallest one.

The sequence a1,a2,...,an is lexicographically smaller than sequence b1,b2,...,bn, if there is such number i from 1 to n, that ak=bk for 1≤k<i and ai<bi.

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

The first contains an integer n (1≤n≤100000) -- the length of the sequence. The second line contains n integers a1,a2,...,an (1≤ai≤109).

Output
For each test case, output n integers b1,b2,...,bn (1≤bi≤109) denoting the lexicographically smallest sequence.

Sample Input
3
1
10
5
5 4 3 2 1
3
1 3 5

Sample Output
1
1 1 1 1 1
1 2 3

Source
BestCoder Round #84

Recommend
wange2014   |   We have carefully selected several similar problems for you:  5751 5750 5749 5747 5746 

树状数组:

/*
对于普通的LIS:
for(i):1~n LIS[i]=1;
if j<i and a[j]<a[i]
LIS[i]=LIS[j]+1
因此可知LIS转移需要两个条件
1.(j<i) 序号必须在i之前
2.(a[i]>a[j]) 值必须比a[i]小
利用树状数组的顺序操作:{查找的都是已经出现的,序号在前(满足条件1)}
对于每一个值,查找它在数组中的排名,再去寻找小于它的排名的最大的LIS(满足条件2)
这里利用到了排名,因为这样可以最大限度地压缩C数组的空间
*/
#include <bits/stdc++.h>
using namespace std;
const int Max=1e5+10;
int A[Max],V[Max],L[Max],C[Max],len;
int lowbit(int x) {return x&(-x);}
int Sum(int x)           //求值小于等于x的LIS的最大值
{
    int ret=0;
    while(x>0)
    {
        if(C[x]>ret) ret=C[x];
        x-=lowbit(x);
    }
    return ret;
}
void Add(int x,int d)   //值大于等于x的LIS都改为LIS(x)
{
    while(x<=len)
    {
        if(d>C[x]) C[x]=d;
        x+=lowbit(x);
    }
}
int main()
{
    int T;
    for(scanf("%d",&T);T;T--)
    {
       int n;
       scanf("%d",&n);
       for(int i=1;i<=n;i++)
       {
          scanf("%d",&A[i]);
          V[i]=A[i];
       }
       sort(V+1,V+1+n);
       len=unique(V+1,V+1+n)-(V+1);
       memset(C,0,sizeof(C));
       int ans=1,tmp,xu;
       for(int i=1;i<=n;i++)
       {
           xu=lower_bound(V+1,V+1+len,A[i])-(V);
           tmp=Sum(xu-1)+1;
           L[i]=tmp;
           Add(xu,tmp);
       }
       for(int i=1;i<=n;i++)
       {
          if(i!=1) printf(" ");
          printf("%d",L[i]);
       }
       puts("");
    }
    return 0;
}

dp+二分

/*
以dp[x]代表长度为x的LIS,且dp[x]==LIS长度为x的末尾值
每次都往前取dp[x]中最小的一个,当然在保证x尽可能地大的情况下
因为dp[x]是递增的,所以可以二分,l=1,r=当前最长的LIS
求得当前以小于当前a[i]的最长LIS
*/
#include <bits/stdc++.h>
using namespace std;
const int Max=1e5+10;
int A[Max];
int dp[Max];
int LIS[Max];
void Get_lis(int n)
{
    int i,j,l,r,mid,ans;
    dp[1]=A[1];
    int len=1;
    for(i=2;i<=n;i++)
    {
        if(dp[len]<A[i]) j=++len;
        else
        {
            l=1;r=len;
            ans=0;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(A[i]>dp[mid]&&A[i]<=dp[mid+1])
                {
                    ans=mid;break;
                }
                else if(A[i]>dp[mid]) l=mid+1;
                else r=mid-1;
            }
            j=ans+1;
        }
        dp[j]=A[i];
        LIS[i]=j;
    }
}
int main()
{
    int T;
    for(scanf("%d",&T);T;T--)
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&A[i]);
            dp[i]=0;
        }
        LIS[1]=1;
        Get_lis(n);
        for(int i=1;i<=n;i++)
        {
            if(i!=1) printf(" ");
            printf("%d",LIS[i]);
        }
        puts("");
    }
    return 0;
}

其实还有一种单调队列求最长上升子序列的方法,可是不能用来解这道题

/*
无解。。。
单调队列只能求出总体的LIS长度
*/
#include <bits/stdc++.h>
using namespace std;
const int Max=1e5+10;
int que[Max],LIS[Max];
int main()
{
    int T;
    for(scanf("%d",&T);T;T--)
    {
        int n,x,top=1;
        scanf("%d",&n);
        scanf("%d",&x);
        que[top]=x;
        LIS[1]=1;
        for(int i=2;i<=n;i++)
        {
            scanf("%d",&x);
            if(x>que[top])
            {
                que[++top]=x;
            }
            else
            {
                int l=1,r=top,mid,ans;
                ans=0;
                while(l<=r)
                {
                    mid=l+(r-l)/2;
                    if(que[mid]<x) l=mid+1,ans=mid;
                    else r=mid-1;
                }
                que[ans]=x;
            }
        }
        cout<<top<<endl;
    }
    return 0;
}
时间: 2024-07-30 10:11:17

hdu 5748(求解最长上升子序列的两种O(nlogn)姿势)的相关文章

最长上升子序列的两种解法

常规思路: 定义 dp[i]:=以a[i]为末尾元素的最长上升子序列的长度, 以a[i]结尾的上升序列是: 只包含a[i]的序列, 在满足j<i并且aj<ai的以aj为末尾的上升子序列末尾追加上ai得到的子序列. 两者之一. 这样递推关系:dp[i]=max(1,dp[j]+1|j<i&&a[j]<a[i])   就能在O(n^2)的时间内解决问题. 1 #include <iostream> 2 #include <cstdio> 3 #i

动态规划求解最长公共子序列

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划求解的问题,经分解得到的子问题往往不是互相独立的.若用分治法来解决这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费指数时间.然而,不同子问题的数目常常只有多项式量级.在用分治法求解时,有些子问题被重复计算了许多次.如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,从而得到多项式

hdu 1025 dp 最长上升子序列

1 //Accepted 4372 KB 140 ms 2 //dp 最长上升子序列 nlogn 3 #include <cstdio> 4 #include <cstring> 5 #include <iostream> 6 using namespace std; 7 const int imax_n = 500005; 8 int dp[imax_n]; 9 int d[imax_n]; 10 int a[imax_n]; 11 int n; 12 int len

【算法导论之七】动态规划求解最长公共子序列

一.动态规划的概念 动态规划(Dynamic Programming)是通过组合子问题的解而解决整个问题的.分治算法是指将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原始问题的解,与此不同,动态规划适用于子问题不是独立的情况,也就是各个子问题包含公共的子问题.在这种情况下,采用分治法会做许多不必要的工作,即重复地求解公共地子问题.动态规划算法对每个子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案. 动态规划通常应用于最优化问题.此类问

hdu 1160 排序 + 最长上升子序列

题意: 输出体重上升而速度下降的最长子序列 题意: 先按照结构体升序排序体重,之后用dp对速度求最长下降子序列即可. 代码: #include <set> #include <map> #include <cmath> #include <stack> #include <queue> #include <string> #include <vector> #include <cstdio> #include

动态规划求解最长递增子序列的长度

一,问题描述 给定一个序列,求解它的最长 递增 子序列 的长度.比如: arr[] = {3,1,4,1,5,9,2,6,5}   的最长递增子序列长度为4.即为:1,4,5,9 二,算法分析 有两种方式来求解,一种是转化为LCS问题.即,首先对数组排序,将排序后的结果存储在辅助数组中.排序时间复杂度O(NlogN),排序后的数组与原数组组成了LCS(N,N)问题.解决LCS问题的时间复杂度为O(N^2),故整个算法的时间复杂度为O(N^2),空间复杂度为O(N) 另一种方式是直接用DP求解,算

动态规划求解最长公共子序列问题

LCS是求两个字符串,最长的公共部分,中间可以间隔其他的元素.例如,字符串s1=''mzjawxu'',s2=''xmjyauz'',仔细分析下,大体可以看出最长公共子序列是''mjau'',我们需要一套科学严格的方法来求解.这个问题是DP问题(动态规划问题).动态规划问题:就是当前问题的求解依赖于上一个子问题,上一个子问题又依赖于前一个子问题,子问题无限递归. 记: Xi=﹤x1,?,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀) Yj=﹤y1,?,yj﹥即Y序列的前j个字符 (1≤j≤n

!HDU 1513 Palindrome--dp--(最长公共子序列模型)

题意:给定一个字符序列,求最少添加多少个字符能让它变成对称序列 分析:这题的做法竟然是把序列颠倒之后求最长公共子序列,然后n-dp[n][n]就是答案.记住这种做法. 在这里再说一次最长公共子序列的做法:dp[i][j]表示序列1的前i个字符和序列2的前j个字符比较时的最长公共子序列的长度,状态转移公式:1.当a[i]==b[j]时,dp[i][j]=dp[i-1][j-1]+1:2.否则,dp[i][j]=max(dp[i-1][j],dp[i][j-1]) 代码: #include<iost

算法练习--- DP 求解最长上升子序列(LIS)

问题描写叙述: 对于2,5,3,1,9,4,6,8,7,找出最长上升子序列的个数 最长上升子序列定义: 对于i<j i,j∈a[0...n] 满足a[i]<a[j] 1. 找出DP公式:dp[i] = dp[j] + 1  (j<i && a[j]<a[i] && dp[i] < dp[j]+1) 2.实现代码 void Main() { DP_LIS(); Console.WriteLine(dpArr); } static int[] ar