LOJ 3276 JOISC 2020 Day2 遗迹 题解 (计数DP)

LOJ链接

UOJ链接

观察一下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();
}
posted @ 2023-03-01 21:17  LegendStane  阅读(45)  评论(0编辑  收藏  举报