大江东去,浪淘尽,千古风流人物。故垒西边,人道是,三国周郎赤壁。乱石穿空,惊涛拍岸,卷起千堆雪。江山如画,一时多少豪杰。遥想公瑾当年,小乔初嫁了,雄姿英发。羽扇纶巾,谈笑间,樯橹灰飞烟灭。故国神游,多情应笑我,早生华发。人生如梦,一尊还酹江月。

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;
}
posted @ 2019-11-02 17:29  White_star  阅读(171)  评论(0编辑  收藏  举报
}