[DP记录]AT2366 [AGC012F] Prefix Median
command_block
2021-03-02 08:53:14
**题意** : 给定一个长为 $2n-1$ 的序列 $a$ ,定义中位数序列 $b={\rm midsq}(a)$ 如下 :
- $|b|=n$
- $b_i=\{a_1,a_2,...,a_{2i-1}\}$ 的中位数。
给出序列 $a$ ,可以将其任意打乱,问能产生多少个本质不同的 ${\rm midsq}(a)$。
答案对 $10^9+7$ 取模。
$n\leq 50$ ,时限$\texttt{2s}$。
------------
AGC 的计数日常 : 这也能数???
- 先考虑 $a$ 中没有重复元素的情况,即 $a_i=i$。
先来考察怎样的 $b$ 是可能被生成的。
生成 $b={\rm midsq}(p)$ 的方法可以理解为 ,先令 $b_1=p_1$ ,然后每插入两个 $p$ 就将 $b$ 重新设置为当前的中位数。
在求解 $b_i$ 时,若插入的两个数字都比 $b_{i-1}$ 大,则中位数会变为 $b_{i-1}$ 的后继,若都比 $b_{i-1}$ 小,则变为其前驱,一大一小则不变。
最终,$b_n$ 会等于整个 $a$ 序列的中位数。
- $b_i$ 是 $p_{1\sim i}$ 中 $b_{i-1}$ 的前驱或后继,或等于 $b_{i-1}$。
等价于 : 对于任意的 $j<i$ 都**不满足** $b_i<b_j<b_{i+1}$ 或 $b_{i+1}<b_j<b_i$ 。
然而,某些时候并不能找出“两个比 $b_{i-1}$ 大/小” 的数字。因此,无法总是做到向 前驱/后继 移动。
根据这个限制,可以发现 $i\leq b_i\leq 2n-i$。
这两个条件配合在一起,正是 $b$ 序列能被生成的**充要条件**。
- 充分性(简略)证明
对于在 $b$ 中没有出现的 $a$ 中元素,记为集合 $S$。
构造时,若 $b_i=b_{i-1}$ ,则从 $S$ 中拿出最小值 $u$ 和最大值 $v$。
若 $b_i<b_{i-1}$ ,则加入 $b_i$ ,并从 $S$ 中拿出最小值 $u$。
若 $b_i>b_{i-1}$ ,则加入 $b_i$ ,并从 $S$ 中拿出最大值 $v$。
根据 $i\leq b_i\leq 2n-i$ 这里能保证总是有 $u<b_i<v$。
- 于是,我们将问题转化为 :
统计满足下列条件的 $b$ 序列的个数。
- (将序列 $a$ 排序) $b_i\in\{a_i,a_{i+1},...,a_{2n-1}\}$。
- 对于任意的 $j<i$ 都不满足 $b_i<b_j<b_{i+1}$ 或 $b_{i+1}<b_j<b_i$
考虑 $\rm DP$。
若按照 $b_1\sim b_n$ 的顺序考虑,第二条限制将会难以处理。不妨以 $b_n\sim b_1$ 的顺序考虑。
对于 $\{b_i,b_{i+1},...,b_n\}$ ,他们对 $\{b_1,b_2,...,b_{i-1}\}$ 产生的约束是 : 对于每一组 $b_i,b_{i-1}$ ,区间 $(b_i,b_{i-1})$ (或区间 $(b_{i-1},b_i)$)中不能再选择数。
(这些开区间的并集会形成一个中间有些断点的区间)
于是,记 $dp[i][l][r]$ 表示考虑了 $b_{i\sim n}$ ,$b_i$ 左侧还剩 $l$ 个数可选,右侧还剩 $r$ 个数可选的方案数。
转移时,暴力枚举 $b_i$ 即可。复杂度为 $O(n^4)$。
现在考虑 $a$ 中有重复数的情况。若将它们按照出现顺序比较大小,序列的合法判据不变,但序列的等价判据改变。
因此,$l,r$ 要改成 “去重后” 还有 $l$ 或 $r$ 个数,
```cpp
#include<algorithm>
#include<cstdio>
#define MaxN 105
using namespace std;
const int mod=1000000007;
int n,a[105],cl[105],cr[105]
,dp[55][105][105];
int main()
{
scanf("%d",&n);
for (int i=1;i<=2*n-1;i++)scanf("%d",&a[i]);
sort(a+1,a+2*n);
for (int i=1;i<=2*n-1;i++)
cl[i]=cl[i-1]+(a[i]!=a[i-1]);
for (int i=2*n-1;i;i--)
cr[i]=cr[i+1]+(a[i]!=a[i+1]);
int m=cl[2*n-1];
dp[n][cl[n]][cr[n]]=1;
for (int i=n-1;i;i--)
for (int l=1;l<=m;l++)
for (int r=1;r<=m;r++)
if (dp[i+1][l][r]){
int sav=dp[i+1][l][r];
dp[i][l][r]=(dp[i][l][r]+sav)%mod;
//左右两侧能选择的最后一个数,一定都是 b(i+1) 本身。若选择,则不会产生新的断点。
for (int k=cl[i];k<l;k++)
dp[i][k][r+1]=(dp[i][k][r+1]+sav)%mod;
//若选择一个靠左的 bi , 则会给 r 增加一个漏洞
for (int k=cr[2*n-i];k<r;k++)
dp[i][l+1][k]=(dp[i][l+1][k]+sav)%mod;
//若选择一个靠右的 bi , 则会给 l 增加一个漏洞
}
int ans=0;
for (int l=1;l<=m;l++)
for (int r=1;r<=m;r++)
ans=(ans+dp[1][l][r])%mod;
printf("%d",ans);
return 0;
}
```