AcWing 5460. 连续整数序列【哈希dp】

文章讲述了如何使用线性动态规划解决给定整数序列中找到一个最长连续递增子序列的问题,涉及到哈希表的使用以及转移数组的记录策略。解题关键在于定义dp状态并维护转移信息,确保正确处理大值范围和转移下标的准确性。
摘要由CSDN通过智能技术生成

原题链接:https://www.acwing.com/problem/content/5463/

连续整数序列指的是由若干个连续正整数组成的递增序列,例如 [1],[3,4,5],[7,8,9,10,11] 都是合法的递增序列。

给定一个长度为 n 的正整数序列 a1,a2,…,an,请你找到该序列的一个尽可能长的子序列,使得该子序列恰好是一个连续正整数序列。

注意,子序列不一定连续。

输入格式

第一行包含整数 n。

第二行包含 n个整数 a1,a2,…,an。

输出格式

第一行输出一个整数 k,表示满足条件的子序列的最大可能长度。

第二行输出 k 个整数,表示该子序列所包含的所有元素在原序列中的下标

所有下标按照升序顺序输出。

如果答案不唯一,输出任意合理答案均可。

数据范围

前 5 个测试点满足 1≤n≤10
所有测试点满足 1≤n≤2×1e5,1≤ai≤1e9。

输入样例1:
7
3 3 4 7 5 6 8
输出样例1:
4
2 3 5 6
输入样例2:
6
1 3 5 2 4 6
输出样例2:
2
1 4
输入样例3:
4
10 9 8 7
输出样例3:
1
1
输入样例4:
9
6 7 8 3 4 5 9 10 11
输出样例4:
6
1 2 3 7 8 9
解题思路:

一看题目就是很明显的线性dp,直接定义f[i]表示以数值i结尾的连最长续递增序列的长度,然后f[i]=max(f[i],f[i-1]+1),正常情况下这样就解决了,但是这个题目有一个比较麻烦的地方就是a[i]的值域比较大,最大是1e9,我们肯定不能定义一个大小为1e9的数组,这个时候我们可以使用将dp数组f定义为哈希表类型,到这里题目还并没有解决完,题目还要求输出最长连续递增序列,也就是还要记录转移,为了方便我们定义一个数组g,定义g[i]表示以当前下标i处的值a[i]结尾的最长递增子序列是从哪个位置转移过来的,当前位置值为a[i],肯定是从值v=a[i]-1转移过来的,但是数组a中可能有多个a[i]-1,每个a[i]-1处的长度都可能不一样,所以我们的g[i]记录转移记录的是下标,而不是值,g数组记录转移下标是因为后面需要输出最长递增序列的下标,如果记录的是值可能会导致出现错误,这个是需要注意的一个点,例如如下例子[1,2,3,2,3,4,5],这里以a[6]=4结尾的最长递增序列的长度为4,但是4是从a[5]=3转移过来的,而不是从a[3]=3转移过来的,如果g数组记录的是转移的值,如果写法不当,很可能会导致我们将转移错误的写法从a[3]=3转移过来,就会导致答案错误,所以我们的g数组最好记录的是转移的下标,而不是值,而且我们f[a[i]-1]记录的以a[i]-1结尾的最长递增序列的长度,最长的a[i]可能在最长的a[i-1]前面,如果用值来记录转移,那么a[i]可能会转移到他后面的a[i]-1,这肯定是不对的,a[i]肯定只能从前面的a[i]-1转移过来,例如[2,3,4,5,1,2,3,4],这里的4有俩个,这里的5肯定是从前面的4转移过来的,肯定不可能从后面的4转移过来,但是后面的4记录的长度是4比前面4记录的长度为3要长,如果转移记录的是值,写法不当就会出现这种错误,到这里这个题目就分析的差不多了,具体分析见代码处。

时间复杂度:dp时间复杂度为O(n),n=2e5,这个时间是可以过的,注意这里的g数组也是可以使用哈希表记录的,但是哈希表比数组效率低,所以能使用数组就尽量使用数组,这样效率更高,对于一些时间卡的比较紧的题目,就需要使用到这种技巧来优化时间。

空间复杂度:使用一个g数组和一个哈希表f,空间复杂度为O(n)。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>

using namespace std;

const int N=2e5+10;

int n;
int a[N];
unordered_map<int,pair<int,int>>f;
int g[N];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    //f[i]表示以值i结尾的最长连续递增序列的长度,g[i]记录的是从哪个下标转移过来的
    for(int i=1;i<=n;i++)
    {
        int u=a[i],v=a[i]-1;
        if(f.count(v)){  //如果a[i]-1在前面出现过
            if(f.count(u)){  //如果a[i]在前面出现过
                if(f[v].first+1>f[u].first){  //更新状态,并且记录转移
                    f[u]={f[v].first+1,i};
                    g[i]=f[v].second;
                }
            }else {   //如果a[i]在前面没有出现过,直接更新f[a[i]]
                f[u]={f[v].first+1,i};
                g[i]=f[v].second;
            }
        }else {  //如果a[i-1]在前面没有出现过,那么以当前a[i]结尾的最长长度就是1,自己是第一个数,自己转移到自己
            f[u]={1,i};
            g[i]=i;
        }
    }
    
    int ans=0,v=0;  //ans记录最长连续递增序列的长度,v记录的是下标
    for(auto t:f){
        if(t.second.first>ans){
            ans=t.second.first;
            v=t.second.second;
        }
    }
    cout<<ans<<endl;
    
    vector<int>nums;
    while(true)
    {
        nums.push_back(v);
        if(v==g[v])break;  //当转移下标是自己时,说明已经到达答案序列的第一个数,跳出循环
        v=g[v];
    }
    //是从结果往前找的答案序列,所以这里需要倒序输出
    for(int i=nums.size()-1;i>=0;i--)
        printf("%d ",nums[i]);
    return 0;
}
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值