AT2301 [ARC068D] Solitaire

洛谷题面

数学+性质推导好题。

不画图就推不懂系列.jpg

题目大意

\(1\sim n\) 顺序加入双端队列(每次可加头可加尾),再删除(每次可删头可删尾),求有多少种删除序列,使得 \(1\) 是第 \(m\) 个被删的。

题目分析

以第 \(m\) 个数为界,令前 \(m-1\) 个数是左部序列,后 \(n-m-1\) 个数是右部序列;令一个满足条件的序列是最终序列。

可以知道:

性质 \(1\):左右部序列弹出时都分别保持单调递减。

原因:因为我们插入的时候就是按顺序插的,所以左部序列单调递减(往左边插入的数不断变大),右部序列单调递增(往右边插的数不断变大)。最后弹出时左部序列从左边开始弹,右部序列从右边开始弹,故左右部序列弹出时都分别保持单调递减。


性质 \(2\)

如图:

红色块的最小值大于青色块的最大值,即后 \(n-m-1\) 个数最大值小于前 \(m-1\) 个数中某一个色块的数的最小值。

原因

本来不是很显然,但是有了图之后瞬间一目了然了。

毕竟左边单调递减,右边单调递增嘛。


说了这么多,最终序列的构成部分是什么呢?

\(\rm Part~1\)

对于前 \(m-1\) 个数:

\(dp_{i,j}\) 表示前 \(m-1\) 个数确定了 \(i\) 个数且这些数的最小值为 \(j\) 时,此时 \(j\) 在红色块的方案数。于是可知蓝色块已经满足了性质 \(2\)

现在又来了一个数 \(x\)。如果 \(x\le j\),那么直接放到红色块的末尾,满足定义,此时去更新了 \(dp_{i+1,1\sim j-1}\);除非 \(x=j\),否则只能把 \(x\) 插在蓝序列的中间,但这种情况之前肯定算过了。所以只需要更新 \(dp_{i+1,1\sim j}\)

\(\rm Part~2\)

对于后 \(n-m-1\) 个数,这些数随便弹弹就好了,弹 \(n-m-1\) 次,每次选序列两边,共 \(2^{n-m-1}\)

代码

//2022/3/18
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <climits>//need "INT_MAX","INT_MIN"
#include <cstring>//need "memset"
#include <numeric>
#include <algorithm>
#define int long long
#define enter putchar(10)
#define debug(c,que) cerr << #c << " = " << c << que
#define cek(c) puts(c)
#define blow(arr,st,ed,w) for(register int i = (st);i <= (ed); ++ i) cout << arr[i] << w;
#define speed_up() cin.tie(0),cout.tie(0)
#define mst(a,k) memset(a,k,sizeof(a))
#define Abs(x) ((x) > 0 ? (x) : -(x))
const int mod = 1e9 + 7;
inline int MOD(int x) {
	if(x < 0) x += mod;
	return x % mod;
}
namespace Newstd {
	char buf[1 << 21],*p1 = buf,*p2 = buf;
	inline int getc() {
		return p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1 << 21,stdin),p1 == p2) ? EOF : *p1 ++;
	}
	inline int read() {
		int ret = 0,f = 0;char ch = getc();
		while (!isdigit(ch)) {
			if(ch == '-') f = 1;
			ch = getc();
		}
		while (isdigit(ch)) {
			ret = (ret << 3) + (ret << 1) + ch - 48;
			ch = getc();
		}
		return f ? -ret : ret;
	}
	inline void write(int x) {
		if(x < 0) {
			putchar('-');
			x = -x;
		}
		if(x > 9) write(x / 10);
		putchar(x % 10 + '0');
	}
}
using namespace Newstd;
using namespace std;

const int ma = 2005;
int dp[ma][ma];//dp[i][j]:前 m - 1 个数中,确定了前 i 个数且这些数的最小值为 j 的方案数
int n,m;
inline int ksm(int n,int m,int p) {
	int res = 1;
	for (;m;m >>= 1ll) {
		if (m & 1ll) {
			res = res * n % p;
		}
		n = n * n % p;
	}
	return res % p;
}
#undef int
int main(void) {
#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
#endif
	#define int long long
	n = read(),m = read();
	for (register int i = 1;i <= n; ++ i) dp[1][i] = 1;
	for (register int i = 1;i < m - 1; ++ i) {
		dp[i + 1][n - i + 1] = dp[i][n - i + 1];
		for (register int j = n - i;j >= 2; -- j) {
			dp[i + 1][j] = MOD(dp[i + 1][j + 1] + dp[i][j]);
		}
	}
	int ans = 0;
	for (register int i = 2;i <= n - m + 2; ++ i) ans = MOD(ans + dp[m - 1][i]);
	if (m == 1) ans = 1;
	if (n == m) printf("%lld\n",ans);
	else printf("%lld\n",MOD(ans * ksm(2,n - m - 1,mod)));

	return 0;
}
posted @ 2022-03-18 22:17  Coros_Trusds  阅读(41)  评论(0编辑  收藏  举报