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
#include
#include //need "INT_MAX","INT_MIN"
#include //need "memset"
#include
#include
#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;
}