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\),至于红色序列,任何一个\(<j\)的数都满足要求,因为他一定是所有选的数中最小的,所以没有不存在的情况
然后前缀和优化就可以AC了
#include<bits/stdc++.h>
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<a;++i)
#define il inline
#define int long long
const int inf=0x3f3f3f3f,N=2010,mod=1e9+7;
int n,m,dp[N];
il void read(int &x){
x=0;char c=getchar(),f=1;
while(!isdigit(c)){ if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)){ x=x*10+c-'0'; c=getchar(); }
x*=f;
}
signed main(){
read(n),read(m);
dp[n]=1;
go(i,1,m){
com(j,n,1){
if(n-j+1<i) dp[j]=0;
else (dp[j]+=dp[j+1])%=mod;
}
}
int ans=1;
go(i,1,n-m-1) ans=ans*2%mod;
printf("%lld",ans*dp[1]%mod);
return 0;
}
一份暴力代码帮助自己理解
#include<bits/stdc++.h>
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<a;++i)
#define il inline
#define int long long
const int inf=0x3f3f3f3f,N=2010,mod=1e9+7;
int n,m,dp[N][N];
il void read(int &x){
x=0;char c=getchar(),f=1;
while(!isdigit(c)){ if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)){ x=x*10+c-'0'; c=getchar(); }
x*=f;
}
signed main(){
read(n),read(m);
go(i,1,n) dp[1][i]=1;
go(i,2,m){
go(j,1,n){
if(n-j-i+1>=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;
}