1 条题解

  • 0
    @ 2025-8-24 23:05:04

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar yummy
    这个人是时代的眼泪,什么也没有留下

    搬运于2025-08-24 23:05:04,当前版本为作者最后更新于2024-10-13 13:23:10,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    D. 配对序列 (pairing) 官方题解

    本题考查的主要知识点有:

    • 【4】简单一维动态规划

    送分

    读题后不难发现测试点 1010 的输出为 00

    对于测试点 11 11,一个数可以出现在答案中当且仅当该数字出现了 22 次。所以统计出现至少两次的数字种数再乘以 22 即可。

    2 2 个测试点可以直接枚举子序列,然后判断是否符合题意,时间复杂度 O(n2n)O(n2^n)

    如果考场上没有任何思路,那么这 2020 分可以尝试拿到手。

    朴素的 DP

    题目也是让你求某种子序列的个数,类似求最长上升子序列,我们考虑 DP。

    f(i)f(i) 为以 ii 结尾的最长配对子序列的长度,则每次转移要枚举 ii 的上一个数的位置 jj 和上上个数的位置 kk,如果 ai=aja_i=a_jajaka_j\ne a_k,那么 fif_i 可以变成 max(fi,fk+2)\max(f_i, f_k+2)。该方法时间复杂度为 O(n3)O(n^3),可以通过测试点 151\sim 5 拿到 2525 分,如果结合送分,可得 3535 分。


    我们发现时间复杂度瓶颈在于每次转移需要枚举两个数。如果我们能把长度为奇数的子序列也写进某种 DP 里,那么每次转移只要枚举上一个数的位置。

    even(i)even(i) 表示以 ii 结尾的最长配对子序列长度,odd(i)odd(i) 表示以 ii 结尾,除了长度是奇数以外都满足配对子序列的要求的、最长子序列长度。

    转移 even(i)even(i) 时,枚举所有 j<ij<i,如果 aj=aia_j=a_i,那么用 odd(j)+1odd(j)+1 去更新 even(i)even(i)

    转移 odd(i)odd(i) 时,枚举所有 j<ij<i,如果 ajaia_j\ne a_i,那么用 even(j)+1even(j)+1 去更新 odd(i)odd(i)

    时间复杂度 O(n2)O(n^2),可得 4545 分,结合送分可得 5555 分。

    更优秀的 DP

    经过观察,发现如果 ai=aja_i=a_j,且 i<ji<j,那么 jj 处的 DP 值一定大于 ii 处的 DP 值(因为 jj 的转移范围大于 ii 的转移范围)。因此转移 even(i)even(i) 时,我们只需要找到 ii 之前最近的aia_i 相等的 aja_j 进行转移。实现上,在 ii 从前往后扫描的过程中,用 prevpre_v 来记录 aa 中上一次出现数 vv 的位置即可。

    对于 odd(i)odd(i),如果是测试点 121412\sim 14,那么只需要在 ii 从前往后循环的同时记录下 even(i)even(i) 的最大值即可——毕竟因为所有数最多出现 22 次,如果 aia_ i 是第一次出现,那么 ii 之前的 jj 都有 ajaia_j\ne a_i;而如果 aia_i 是第二次出现,这个 odd(i)odd(i) 就不可能再被某种 even(j)even(j) 调用,正确性也就不重要了。

    然而对于所有测试点,只记录 even(i)even(i) 最大值(记为 even(J) even(J))是不够的——如果 ai=aJa_i=a_J,那么这时就要转而求“次大值”。总而言之,我们需要在 ii 从前往后循环的同时,记录 aJa_J 不同的 even(J)even(J) 前二大。

    这样,总时间复杂度就是 O(n+maxai)O(n+\max a_i) 的了,可以通过。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=500005;
    int n,a[N],even[N],odd[N],pre[N];
    int main(){
    	scanf("%d",&n);
    	int b1=0,b2=0;
    	odd[0]=-1e9;
    	for(int i=1;i<=n;i++){
    		scanf("%d",&a[i]);
    		if(a[b1]==a[i])
    			odd[i]=even[b2]+1;
    		else odd[i]=even[b1]+1;
    		even[i]=odd[pre[a[i]]]+1;
    		if(even[i]>=even[b1]){
    			if(a[i]!=a[b1])
    				b2=b1;
    			b1=i;
    		}
    		else if(even[i]>=even[b2] && a[i]!=a[b1])
    			b2=i;
    		pre[a[i]]=i;
    	}
    	printf("%d\n",even[b1]);
    	return 0;
    }
    
    • 1

    信息

    ID
    10821
    时间
    1000ms
    内存
    512MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者