cf 1515E. Phoenix and Computers (dp计数|分段合并)

题目大意:

给定若干计算机1~n,每次操作可以选择一台计算机\(i\),并将其打开。若计算机\(i+1\)\(i-1\)已经被打开,那么\(i\)会自动打开。问有多少种操作序列,使得n台计算机全部打开。

题目链接:

[]: https://codeforces.com/contest/1515/problem/E "传送门"

题目思路:

计算机1~n的最终状态一定是:一段计算机手动打开+一台计算机自动打开+一段计算机手动打开+...+一段手动+一台自动+一段手动。

考虑全部手动打开一段计算机的方案数,假设这一段共有k台计算机,对其分别进行编号 1~k 。

设第一台手动开启的计算机为\(i(i∈[1,k])\), 对于区间\([i+1,k]\)的计算机的打开顺序必然是 \(i+1,i+2,..,k\) ,否则必然会在中间产生一个自动打开的计算机,同理,对于区间\([1,i-1]\)的计算机的打开顺序必然是 \(i-1,i-2,..,1\) ,而这两个区间的打开顺序在保证各自的相对顺序的前提下,是可以相互穿插的,方案数为\(C_{k-1}^{i-1}\)(考虑穿插结果,共有k-1为位置,选择i-1个位置放置序列 \(i-1,i-2,..,1\),剩下放置 \(i+1,i+2,..,k\) )。

那么手动打开一段计算机的总方案数为\(C_{k-1}^{0}+C_{k-1}^{1}+C_{k-1}^{2}+...+C_{k-1}^{k-2}+C_{k-1}^{k-1} = 2^{k-1}(二项式定理)\);

接下来考虑如何计算"一段计算机手动打开+一台计算机自动打开+一段计算机手动打开",可以先计算出前几段的方案数,再合并新的一段手动打开的计算机,

定义\(f(i,j)\)表示前\(i\)个计算机中有\(j\)个计算机是手动打开的方案数,

转移方程:\(f(i+1+k,j+k) ~=~f(i,j) \times 2^{k-1} \times C^k_{j+k} ~~~~\),\(k\)为要合并的下一段手动打开计算机的数量,\(2^{k-1}\)为下一段计算机内部的方案数,\(C^k_{j+k}\)为将下一段计算机与之前手动打开的计算机相互穿插并保证各自的相对顺序不变的方案数;

最终答案 = \(\sum _{i=0}^nf(n,i)\)

AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pLL;
typedef pair<double,double> pdd;
const int N=2e3+5;
const int M=65;
const int inf=1e9;
const LL mod=924844033;
const double eps=1e-8;
const long double pi=acos(-1.0L);
#define ls (i<<1)
#define rs (i<<1|1)
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define mk make_pair
#define mem(a,b) memset(a,b,sizeof(a))
LL read()
{
    LL x=0,t=1;
    char ch;
    while((ch=getchar())<'0'||ch>'9') if(ch=='-') t=-1;
    while(ch>='0'&&ch<='9'){ x=(x<<3)+(x<<1)+ch-'0'; ch=getchar(); }
    return x*t;
}
LL f[N][N],g[N],c[N][N];
int main()
{
    int n=read(),m=read();
    g[0]=1; c[0][0]=1;
    for(int i=1;i<=n;i++) g[i]=g[i-1]*2%m;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++)
            c[i][j]=(c[i-1][j]+(j?c[i-1][j-1]:0))%m;
    for(int i=1;i<=n;i++) f[i][i]=g[i-1];//初始化,求出第一段手动打开计算机的方案,接下来计算合并。
    for(int i=1;i<n;i++)
    {
        for(int j=1;j<=i;j++)
            for(int k=1;k+i<n;k++)
                f[i+k+1][j+k]=(f[i+k+1][j+k]+f[i][j]*g[k-1]%m*c[j+k][k]%m)%m;
    }
    LL ans=0;
    for(int i=0;i<=n;i++) ans=(ans+f[n][i])%m;
    printf("%lld\n",ans);
    return 0;
}

最近,在别的大佬处学习了一种新姿势来解决这类题:https://www.cnblogs.com/Robert-JYH/p/14727596.html

posted @ 2021-07-25 22:00  DeepJay  阅读(70)  评论(0编辑  收藏  举报