iwtgm-15

题目链接
这个dp题还是想多说一点,感觉之前写的还是不够清晰和透彻
先提一嘴,感觉这个dp不同于一般的dp,不是从1递推到n,个人理解更像是桶,后面会有很神奇的转移,真的太巧妙了
先解决一些局部问题吧
首先来想想重复的情况
如:0,1,1
这个例子中1是重复的,我们的dp转移状态是dp[i][0]+=dp[i][0]
注意这里是+=,可以这样理解:原来的情况其实被保存下来了,然后再把当前字符加到原来的所有情况中,那么就又出现了dp[i][0]个情况
我们选序列出来可以是不连续的嘛,所以所有情况记录下来再加上当前字符实际上满足了这个限制
比如说现在是第5个字符,前面只有1个字符的情况被保留了下来,我现在加上这个字符,就有一种情况是1,5,满足不连续取的操作,好,这个就说到这里

然后说是序列有两种情况,用dp[i][0/1]来表示
0,0,1,1,2,2这种递增的情况
考虑它的dp转移状态:
dp[i][0]+=dp[i][0];这个转移的是重复字符,如果这个字符已经出现过,那么在之前的所有情况上再加上现在的1个字符,那么就又出现了dp[i][0]这么多的情况
dp[i][0]+=dp[i-1][0]:前面我们提到桶的概念,假设0,1,1,用桶来解释就是0是dp[0][0],两个1都在dp[1][0]里面,相当于现在直接把当前的1放在0的后面,是重复元素的第一个,显然不与前面的情况重复(前面的情况0后面至少会有1个1,因为我们在转移状态的时候当前字符是必取的)

然后考虑这种:0,0,1,1,3,3,1,1,3,3
当我们开始不以1来递增的时候,现在取的必然是大的那个数,后面出现的数只能是大的数和前一个数
在这里提一嘴以1开头的,因为我们可以把它归结到第二种状态,请往下看:
在后面的代码部分,我们会把输入的值+1,为什么呢,因为这样操作会更简单,理由再往下看:
0开头是一般情况,因为这个时候这个Mex是比当前数大的,事实上第一种状态的时候,Mex都是比当前出现过的所有数大,把0归为第一种
以1开头,Mex比当前数也就是1小,先通俗地说,后面只能取1了,取其他都不行
因为这种情况下,我们Mex是不能取的,只能取比Mex大1的数,和比Mex小1的数
这里的话Mex=0,1是较大的那个数,-1是较小的那个数,而我们只要非负整数,所以-1不能取,所以可以把1归结为第二种情况了吧
所以我们把输入的值+1就可以不用特判1,让dp转移更具有普遍性
如这个例子,我们开始取3了(没有取2),那么我们后面再取数只能是1和3了
考虑它的dp转移:
dp[i][1]+=dp[i][1];这个理由与前面一样
dp[i+2][1]+=dp[i+2][1];
相当于这个例子吧:1,3,3,1,此时我们有哪些贡献呢,
第一个dp状态相当于这样:1,1,3,3,已经计算过了
此时就是1,3,3,1,加在3的后面,也就是dp[i+2][1],只是把这些贡献放在dp[i+2][1]里,这里可以看出用桶其实一直是升序状态,并没有保持原序列的样子,但其实顺序不是太重要,不影响。因为在后面加数,前面满足的情况都会被加上,位置在哪儿无所谓
第三个dp状态:if(x>1) dp[i][1]+=dp[i-2][0];
与dp[i][0]+=dp[i-1][0]这个情况原理类似,让当前字符成为重复字符的第一个(前面是其他字符)

好了,至此代码说明完毕。
它就是灵活了位置,不是一般的从前往后推,这样的确计数非常高效,去看其他正常递推的是在是复杂且难以理解。

一样的代码,再贴一次吧
注释是之前写的,写得不明白的可以适当忽略

ll dp[N][2];
const int mod=998244353;
void solve()
{
    int n;cin>>n;
    for(int i=0;i<=n+1;i++){
        dp[i][0]=0;dp[i][1]=0;
    }
    dp[0][0]=1;//初始化,因为0和1可以作为开头
    for(int i=1;i<=n;i++){
        int x;cin>>x;x++;//加1操作后会比较方便,因为1可以作为开头,但它后面就只能一直取1,因为比它小2的是负数,取不了,它相当于是第二种情况
        dp[x][0]+=dp[x][0]+dp[x-1][0];//等号右边的第一项是当前字符已经存在的所有情况,是把x加在这些情况后面。第二项是当前字符直接加在x-1后面,相当于重复的第一个
        dp[x][1]+=dp[x][1];//当前字符是第二种情况,可以加一倍
        dp[x+2][1]+=dp[x+2][1];//就是在原来的所有情况上加上这个字符,写成[x+2]是桶的性质,可以这样等价,所以说是不大正常的dp
        if(x>1)dp[x][1]+=dp[x-2][0];//把当前的字符当作第二种情况的开头,所以x-1是第一种情况的时候,也就是说,第二种情况的0,1,3,1 其实可以等价转化为:0,1,1,3,用桶计数
        dp[x][0]%=mod;
        dp[x][1]%=mod;
        dp[x+2][1]%=mod;
    }
    ll ans=0;
    for(int i=1;i<=n+1;i++){
        ans+=dp[i][0]+dp[i][1];
        ans%=mod;
    }
    cout<<ans<<endl;
}
posted @ 2023-11-05 23:21  WW爆米花  阅读(5)  评论(0编辑  收藏  举报