AtCoder Regular Contest 068F Solitaire dp

版权声明:转吧转吧这条东西只是来搞笑的。。 https://blog.csdn.net/jpwang8/article/details/89204955

Description


有一个双端队列,按照1…n的顺序把n个卡片放进队头或队尾得到一个满的队列
然后每次可以从头或从尾取一个数字取n次,这样得到一个长度为n的序列。问1恰好在k这个位置的方案数

Solution


这题好劲啊
可以发现几个小性质。首先队列里的元素一定是先减后增的,并且由于1在k这里,所以前k-1个数字必须是一个或两个单调下降序列,而后面n-k-1个随便从队列两端选。然后我就不会做了。。
具体做法可以看这个blog

讲一下我的理解吧,可能不太对。
考虑设f[i,j]表示选了i个数字,最小的是j的方案数。假设我们已经得到了2个单调的序列,那么第i个一定只能放一个小于最小值的数字,不然我们只能把它插在某个序列的中间,而这种方案一定已经被算过一次了

那么n3的dp套一个前缀和优化就可以做到n2

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)

const int MOD=1e9+7;
const int N=2005;

int f[N][N],s[N][N];

int ksm(int x,int dep) {
	int res=1;
	for (dep=std:: max(dep,0);dep;dep>>=1,x=1LL*x*x%MOD) {
		(dep&1)?(res=1LL*res*x%MOD):0;
	}
	return res;
}

int main(void) {
	int n,k; scanf("%d%d",&n,&k);
	if (n==k) {
	}
	f[0][n+1]=1;
	drp(j,n+1,1) {
		s[0][j]=s[0][j+1]+f[0][j];
		(s[0][j]>=MOD)?(s[0][j]-=MOD):0;
	}
	rep(i,1,k) {
		rep(j,1,n-i+1) f[i][j]=s[i-1][j];
		drp(j,n+1,1) {
			s[i][j]=s[i][j+1]+f[i][j];
			(s[i][j]>=MOD)?(s[i][j]-=MOD):0;
		}
	}
	int ans=f[k][1]-f[k-1][1];
	(ans<0)?(ans+=MOD):0;
	ans=1LL*ans*ksm(2,n-k-1)%MOD;
	printf("%d\n", ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/jpwang8/article/details/89204955