LOJ 3276 JOISC 2020 Day2 遗迹 题解 (计数DP)
观察一下n次地震的过程,发现最后会有n个石柱高度为0,\(1,2\cdots n\)高度的石柱各有一个。假设现在已经确定了一种初始高度状态,我们来看看最后哪些石柱高度会大于0:
- 首先,初始高度为n的两个石柱中,较右的会留下来,最后高度为n的石柱就是它
- 接下来,最后高度为n-1的石柱是初始高度为n的两个石柱中较左的那个,以及初始高度为n-1的两个石柱中最右的那个
- 以此类推\(\cdots\)
稍微推一下可以总结成以下过程:从大到小枚举每种初始高度,枚举到i时,把初始高度为i的两个石柱加入一个堆中,然后从堆中弹出最右的一个石柱,这个石柱就是最终高度为i的。最后堆中会剩下n个石柱,这些就是最后高度为0的。
知道了这个似乎还是不太好做。我们来看看在确定了初始状态后,能不能方便地确定每个石柱最后的高度。我们维护一个"剩余可用高度集合"S,初始\(S=\{1,2\cdots n\}\)。令第i个石柱的初始高度为\(h_i\),我们从右到左遍历每个石柱i,并在S中找出\(\le h_i\)的最大的元素。如果这个元素不存在,那i号石柱最终的高度就是0;否则最终的高度就是这个元素的值,我们需要把这个元素从S中删除。根据上面总结的过程,这部分也很好理解。用最右边的石柱举个例子吧,由于它在所有的"取出最右"的过程中都能杀穿,所以它一定能抢到最终高度为\(h_{2n}\)的位置。
现在题目钦定了每个石柱最终高度是0(被删除)还是大于0(不被删除),我们要求出满足这些条件的初始状态数。我们用dp来从右到左决定石柱的高度。一个合法的初始状态中每个高度都恰有2个石柱,为了便于dp,我们假设这两个石柱是不同的(相当于假设高度为i的石柱有红蓝两种,分配的时候任选一种不得重复),最后把答案除以\(2^n\)即可。令\(dp_{i,j}\)表示处理完了最靠右的i个石柱,S中的前j个元素都已经被删掉,第j+1个元素没被删掉(如果存在的话),且只决定了最右边的i个石柱中 钦定被删掉的那些 和 钦定没被删掉的那些中最终高度\(\le j\)的那些 的初始高度,其它石柱初始高度待定,此时的方案数。这句话包含的条件有点多,建议多看几遍。令最右的i个石柱中有\(cdel\)个钦定被删除,\(ckeep\)个钦定不被删除。(主动)转移有以下几种情况:
- 当前石柱钦定被删掉。则它的初始高度必须\(\le j\),否则它的最终高度就是j+1了。高度范围\([1,j]\)中一共有2j种石柱,钦定不被删的消耗了j种,钦定被删的消耗了\(cdel\)种,所以一共有\(2j-j-cdel\)种方案。因此,\(dp_{i+1,j}+=dp_{i,j}\cdot(j-cdel)\)。
- 当前石柱钦定不被删掉,且选择初始高度后S被删除的连续前缀长度不变。那么我们就先不选它的初始高度,先待定,以后再选。\(dp_{i+1,j}+=dp_{i,j}\)。
- 当前石柱钦定不被删掉,且选择初始高度后S被删除的连续前缀变长了。我们枚举k,表示S被删除的连续前缀长度从j变成了\(j+k\)。要从S中删除这多出来的k个元素,需要k个石柱,当前石柱占了一个(它刚好从S中删掉了j+1,选择它的初始高度的方案数是\(2k-(k-1)=k+1\)),我们还需要从\(ckeep-j\)个初始高度待定的中选出\(k-1\)个,由于这k-1个之间是有顺序的,所以方案数\(\binom{ckeep-j}{k-1}\cdot (k-1)!\)。还要乘上一个额外的方案数\(f_{k-1}\),其中\(f_i\)表示在\(1-i\)的高度值,\(2i\)种不同石柱中选出i个,使得删掉的S中的元素刚好为\(1-i\)的方案数(注意只是选出,选出元素之间没有顺序)。综上,\(dp_{i+1,j+k}+=dp_{i,j}\cdot (k+1)\cdot \binom{ckeep-j}{k-1}\cdot (k-1)!\cdot f_{k-1}\)。
上面这部分的复杂度是\(O(n^3)\)的。
然后来求一下f。令\(dp2_{i,j}\)表示处理了最大的i种高度,选了j个石柱的方案数。为了保证合法,需要满足\(j总是\ge i\)(即j<i时dp值为0)。转移就枚举接下来这种高度选0、1还是2个即可。\(f_i=dp2_{i,i}\)。这部分时间复杂度\(O(n^2)\)。
总时间复杂度\(O(n^3)\)。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
const LL MOD=1e9+7;
LL qpow(LL x,LL a)
{
LL res=x,ret=1;
while(a>0)
{
if(a&1) (ret*=res)%=MOD;
a>>=1;
(res*=res)%=MOD;
}
return ret;
}
LL n,del[1210],dp2[610][610],f[610],dp[1210][610],fac[1210],inv[1210];
int main()
{
fileio();
freopen("temple.in","r",stdin);
freopen("temple.out","w",stdout);
fac[0]=1;repn(i,1205) fac[i]=fac[i-1]*i%MOD;
rep(i,1203) inv[i]=qpow(fac[i],MOD-2);
cin>>n;
rep(i,n+n+3) del[i]=1;
LL x;
rep(i,n)
{
scanf("%lld",&x);--x;
del[x]=0;
}
dp2[0][0]=1;
rep(i,n+1) for(int j=i;j<=min((LL)i+i,n);++j)
{
(dp2[i+1][j]+=dp2[i][j])%=MOD;
(dp2[i+1][j+1]+=dp2[i][j]*2)%=MOD;
(dp2[i+1][j+2]+=dp2[i][j])%=MOD;
}
rep(i,n+2) f[i]=dp2[i][i];
reverse(del,del+n+n);
dp[0][0]=1;
LL cdel=0,ckeep=0;
rep(i,n+n)
{
rep(j,ckeep+1) if(dp[i][j])
{
if(del[i])
{
if(j-cdel>=0)
(dp[i+1][j]+=dp[i][j]*(j-cdel))%=MOD;
}
else
{
(dp[i+1][j]+=dp[i][j])%=MOD;
repn(k,n-j)
{
LL lftkeep=ckeep-j;
if(k-1>lftkeep) break;
LL val=dp[i][j]*(k+1)%MOD;
if(k-1>0) (val*=fac[lftkeep]*inv[lftkeep-(k-1)]%MOD*f[k-1]%MOD)%=MOD;
(dp[i+1][j+k]+=val)%=MOD;
}
}
}
cdel+=del[i];ckeep+=(1-del[i]);
}
LL ans=dp[n+n][n];
(ans*=qpow(qpow(2,n),MOD-2))%=MOD;
cout<<ans<<endl;
termin();
}