[刷题笔记] Luogu P3205 [HNOI2010] 合唱队

发布时间 2023-08-12 00:41:05作者: SXqwq

Problem

Analysis

一道分类讨论dp

我们发现本题满足大区间包含小区间,区间之间可以互相推导,符合区间dp。

再看看我们需要记录什么?我们发现哪一个数最后放会影响到决策,所以我们需要记录这一层状态。

形式化地,定义\(f_{l,r}\)表示区间\([l,r]\)\(r\)最后放的方案数,那么同理\(f_{r,l}\)就是区间\([l,r]\)\(l\)最后放的方案数。

注意,我们这里提到的“最后放”和题目中放到队列最前面队列最后面同理。我们会在接下来的讲解中继续说明,也许在下文您会明白。

同时,我们定义\(a_i\)表示第\(i\)名同学的身高。

定义完状态,接下来我们分类讨论:

  • \(a_l < a_{l+1}\),显然意味着在区间\([l,r]\)\(l\)可以放在队列最前面,也就是我们所称的“\(l\)最后放”,故\(f_{r,l}+=f_{r,l+1}\)。这里加上的必须是\(f_{r,l+1}\)也就是\(l+1\)最后放的情况,因为此时\(a_l < a_{l+1}\)也就是\(l\)可以最后放,可以转移。

  • \(a_l < a_r\)且满足\(l+1 \ne r\)时,有\(f_{r,l}+=f_{l+1,r}\)\(f_{l,r} += f_{r-1,l}\) 解释一下:当\(a_l < a_r\)成立时,若把\(l\)放在最前面,则和她转移的可以确定的是\(r\)放在最后面,至于\(l\)\(l+1\)转移即可。同理,若将\(r\)放在最后面,和她转移的可以确定的是\(l\)放在最左边,至于\(r\)\(r-1\)转移即可。

    为什么这里必须要满足\(l+1 \ne r\) 呢?反证:当\(l+1 = r\)时,显然\(f_{l,r}=f_{r,l}=1\),而若用上述判断则\(f_{l,r}=f_{r,l}=2\),因为我们初始化使\(f_{i,i}=1(\forall i \in [1,n])\)

  • \(a_r > a_{r-1}\) 和第一条同理,若\(r\) 放在队列最后面,也就是\(r\)“最后放”,则和她转移的一定是\(f_{l,r-1}\),也就满足\(f_{l,r}+=f_{l,r-1}\)

至此,分类讨论完毕。我们现在可以回答开始的问题了,\(l\)“最后放”就把\(l\)放到数组第二维去,\(r\)最后放同理。我们发现此类题目转移的关键是要找到“什么不变”“什么变”,例如\(a_l\)最后放,已知\(a_l < a_{l+1}\),则可以和\(a_{l+1}\)“最后放”时进行转移,至于\(r\)不变即可。

最后\(ans = f_{1,n}+f_{n,1}\),我们显然不确定谁先放,所以两者加起来即可。

别忘了取模。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 10010
#define mod 19650827
using namespace std;
int n;
int a[N];
int f[N][N];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&a[i]);
        f[i][i] = 1;
    }
    for(int i=2;i<=n;i++)
    {
        for(int l = 1;l <= n && l+i-1 <= n ; l++)
        {
            int r = l + i - 1;
            if(a[l] < a[l+1]) f[r][l] += f[r][l+1];
            if(a[l] < a[r] && l+1 != r) {f[r][l] += f[l+1][r];f[l][r] += f[r-1][l];}
            if(a[r] > a[r-1]) f[l][r] += f[l][r-1];
            f[l][r] %= mod;
            f[r][l] %= mod;
        }
    }
    cout<<(f[1][n]+f[n][1])%mod<<endl;
    return 0;
}