ARC068F Solitaire

神仙DP题

首先奉上神仙PuFanyi的博客讲解

然后是我这个菜鸡的个人理解(推荐上面那篇博客,讲的比我好多了)


由于从小到大插入,所以最终序列的两边一定要比中间要大,可以看做一个\(V\)字型序列

为了取出\(1\),我们一定会取完一整个单调的序列和另一个单调的序列的一部分

假装我们已经取完了前\(K\)个数,那么剩下的数是一个单调的序列,选法总数就是\(2^{n-k-1}\),注意当序列只剩一个元素时,队首和队尾是等价的

考虑前\(K\)个数的选法,可以DP

考虑前\(K\)个数构成了两个单调递减的序列,对于确定的\(K\)个数(顺序也是确定的),只要存在一种方案,使得它能够被合法地加入双端队列并合法地取出,那么该序列合法。故我们只需一种最有可能合法的方案即可,若该方案合法,说明整个序列都是合法的

借用PuFanyi的博客中的红色、蓝色和绿色序列的概念,由于蓝色序列的最小值>绿色序列的最大值,所以我们要尽可能把较大的加入蓝色序列。这样最有可能合法

\(f[i,j]\)表示到第i位,最小的一位为\(j\)的方案数,\(j\)即为红色序列末尾

所以考虑队首和队尾,对于较大的一个,即剩下的数中的最大值,如果存在这个大于\(j\)的数,把他放到蓝色序列中,否则放入红色序列中

也可以选择较小的那一个,若其比\(j\)小,将其放入红色序列中

考虑什么时候存在大于\(j\)的最大值。大于j的数有\(n-j\)个,其中\(i-2\)个已经被选,故\(n-j-i+2>0\)\(n-j+1>=i\),至于红色序列,任何一个\(的数都满足要求,因为他一定是所有选的数中最小的,所以没有不存在的情况

然后前缀和优化就可以AC了

#include
using namespace std;

#define go(i,a,b) for(int i=a;i<=b;++i)
#define com(i,a,b) for(int i=a;i>=b;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#define fo(i,a) for(int i=0;i

一份暴力代码帮助自己理解

#include
using namespace std;

#define go(i,a,b) for(int i=a;i<=b;++i)
#define com(i,a,b) for(int i=a;i>=b;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#define fo(i,a) for(int i=0;i=0) dp[i][j]=dp[i-1][j];
            //检查当前是否存在合法且最大的数放入蓝色序列 
            go(k,j+1,n){
                if(n-k-i+1>=0) (dp[i][j]+=dp[i-1][k])%=mod;
                //检查dp[i-1][k]是否合法且k是否为蓝色序列的结尾(即只有蓝色序列的情况) 
            }
        }
    }
    int ans=1;
    go(i,1,n-m-1) ans=ans*2%mod;
    printf("%lld",ans*dp[m][1]%mod);
    return 0;
}

你可能感兴趣的:(ARC068F Solitaire)