题解:P7213 [JOISC2020] 最古の遺跡 3

两种思考方式:

  • 值域维度:从原始高度大往小插入柱子,不确定右边是否有最终高度等于它的柱子,并不好记录状态。

  • 下标维度:从后往前扫描,注意到前面的对后面的后效性很小,此时不难设出一个基础的状压 DP 方程。

为了避免记录后方柱子初始高度出现情况(相同初始高度有且仅有 \(2\) 种),将相同的初始高度拆成两个,颜色不同,强制定序,最后消序(除以 \(2^n\))即可。

Part 1 状压 DP

不妨设 \(f_{i, S}\) 表示后 \(i\) 个柱子,最终高度出现集合为 \(S\) 的方案数。其中,最终高度 \(i\) 出现,那么 \(S_i = 0\),否则 \(S_i = 1\),初始时 \(S\) 为全 \(1\) 串。

此时若 \(i\) 的最终高度 \(a_i\)\(0\),这就意味着在 \(i\) 左边从 \(1\)\(i\) 的初始高度都已经出现过(一段前缀零),设 \(S\) 的前缀 \(0\) 长度为 \(Pre_0(S)\),那么考虑 \(i\) 初始高度的取值有多少种可能:首先 \(i\) 的初始高度必须位于 \(S\) 的前缀 \(0\) 中,所以共有 \(2Pre_0(S)\) 种选择(每种高度 \(2\) 个);然后每一个在前缀 \(0\) 中的柱子又会占用掉一个高度(最终高度在前缀 \(0\) 的柱子初始高度一定在前缀 \(0\) 中,虽然他们有可能是被震下来的,但仍占用了 \(1\) 种高度);最后在 \(i+1\) 以后的,被震成 \(0\) 的柱子,初始高度仍然均在 \([1,Pre_0(S)]\) 之间,减去。最后剩下的就是给 \(i\) 的了,有方程:

\[f_{i,S} = f_{i+1,S} \times (2Pre_0(S) -Pre_0(S)-\sum_{j=i+1}^{2n}[a_j=0]) \]

\(i\) 的最终高度 \(a_i\) 不为 \(0\),枚举 \(i\) 的最终高度 \(j\),设 \(j\) 之后连续一段 \(0\) 的长度为 \(len_j(S)\)\(S_j=1\)),因为如果 \(i\) 的初始高度在 \(j\) 后面一段 \(0\) 中,最后一定会被震成 \(j\),那么有转移方程:

\[f_{i,S'} = f_{i+1,S} \times (2len_j(S) - len_j(S) + 2) \]

解释:如果当前点初始高度在后面 \(len_j(S)\) 个已被占用高度当中,共有 \(2len_j(S)\) 种选择,已经被占了 \(len_j(S)\) 种高度;如果当前柱子初始高度直接就是最终高度,有 \(2\) 种选择(一个高度两种)。

给张图方便理解。

pic1

Part2 优化

考虑第一种转移,仅仅与前缀 \(0\) 长度有关,考虑只记录前缀 \(0\) 长度,设 \(f_{i,j}\) 表示后 \(i\) 个柱子,最终高度出现集合前缀 \(0\) 长度为 \(j\) 的方案数(事实上,这里所谓的前缀 \(0\) 就是其他题解所说的支撑柱,标准柱),第一种(最终高度 \(a_i=0\))状转方程直接照搬。

\[f_{i,j} = f_{i+1,j} \times (2j - j -\sum_{k=i+1}^{2n}[a_k=0]) \]

但是发现第二种情况(最终高度 \(a_i > 0\))不太好办了,当一个柱子最终高度在 \(j+1\) 往上的时候,无法处理出 \(0\) 联通块大小。计数题分步计算不了,先合并捆绑,后定序处理。分类讨论两种情况。

  • \(a_i > j + 1\),此时无法处理,不用考虑,直接合并。

\[f_{i,j} = f_{i+1,j} \]

  • \(a_i = j + 1\),枚举将 \(j+1\) 纳入最终高度出现集合中后,新的前缀 \(0\) 长度为 \(k\),又令 \(val = \sum_{j=i+1}^n[a_j=0]\),令 \(g_i\) 表示将 \(i\) 个初始高度在 \([1,i]\) 中柱子最终可以被震成连续一段的方案数,那么有转移方程:

\[f_{i,k} = f{i+1,j} \times \binom{val - j}{k - j - 1} \times (2(k-j-1)-(k-j-1)+2) \times g_{k-j-1} \]

解释:

  • \(\binom{val - j}{k - j - 1}\):共有 \(val\) 个数可以成为 \(0\)(支撑柱),其中有 \(j\) 个已经确定了,在之前被选入前缀 \(0\) 中,剩下 \(val - j\) 个未确定最终高度和初始高度的柱子要被放入 \(k - j - 1\) 这新的一段前缀 \(0\) 中。

  • \(2(k-j-1)-(k-j-1)+2\):确定当前 \(i\) 号柱子的初始高度,正如在状压 DP 那里讲的,共有 \(2(k-j-1)\) 种高度,被后面的连续 \(0\) 占用了 \((k-j-1)\) 种,剩下 \(2(k-j-1)-(k-j-1)\) 种,除此之外初始高度直接是 \(k\)\(2\) 种可能。

  • \(g_{k-j-1}\) 确定这 \(k - j - 1\) 个新柱子的初始高度和最终高度,由于这些柱子高度变化与外界无关,平移后可以转化为子问题。

同样给一张图:

接下来只用考虑 \(g\) 的转移了,类似于第二种情况。

\[g_i = \sum_{j=1}^{i}g_{i-j}\times g_{j - 1}\times(2(i-j)-(i-j)+2)\times \binom{i - 1}{j - 1} \]

组合意义和上面基本一样,给张图就理解了:

\(O(n^2+n^3)\)

Code

#include<bits/stdc++.h>
#include<ext/pb_ds/hash_policy.hpp>
#include<ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
namespace fast_IO{
    #define IOSIZE (1<<20)
    char ibuf[IOSIZE],obuf[IOSIZE];char*p1=ibuf,*p2=ibuf,*p3=obuf;
    #ifdef ONLINE_JUDGE
    #define putchar(x)((p3==obuf+IOSIZE)&&(fwrite(obuf,p3-obuf,1,stdout),p3=obuf),*p3++=x)
    #endif
    #define isdigit(ch)(ch>47&&ch<58)
    #define isspace(ch)(ch<33)
    template    <typename T>inline T    read(){T s=0;int w=1;char ch;while(ch=getchar(),!isdigit(ch)and(ch!=EOF))if(ch=='-')w=-1;if(ch==EOF)return false;while(isdigit(ch))s=s*1+ch-48,ch=getchar();return s*w;}template<typename T>inline bool read(T&s){s=0;int w=1;char ch;while(ch=getchar(),!isdigit(ch)and(ch!=EOF))if(ch=='-')w=-1;if(ch==EOF)return false;while(isdigit(ch))s=s*10+ch-48,ch=getchar();return s*=w,true;}template<typename T>inline void print(T x){if(x<0)putchar('-'),x=-x;if(x>9)print(x/10);putchar(x%10+48);}inline bool read(char&s){while(s=getchar(),isspace(s));return true;}inline bool read(char*s){char ch;while(ch=getchar(),isspace(ch));if(ch==EOF)return false;while(!isspace(ch))*s++=ch,ch=getchar();*s='\000';return true;}inline void print(char x){putchar(x);}inline void print(char*x){while(*x)putchar(*x++);}inline void print(const char*x){for(int i=0;x[i];i++)putchar(x[i]);}inline bool read(std::string&s){s="";char ch;while(ch=getchar(),isspace(ch));if(ch==EOF)return false;while(!isspace(ch))s+=ch,ch=getchar();return true;}inline void print(std::string x){for(int i=0,n=x.size();i<n;i++)putchar(x[i]);}inline bool read(bool&b){char ch;while(ch=getchar(),isspace(ch));b=ch^48;return true;}inline void print(bool b){putchar(b+48);}template<typename T,typename...T1>inline int read(T&a,T1&...other){return read(a)+read(other...);}template<typename T,typename...T1>inline void print(T a,T1...other){print(a),print(other...);}struct Fast_IO{~Fast_IO(){fwrite(obuf,p3-obuf,1,stdout);}}jyt;template<typename T>Fast_IO&operator>>(Fast_IO&jyt,T&b){return read(b),jyt;}template<typename T>Fast_IO&operator<<(Fast_IO&jyt,T b){return print(b),jyt;}
    struct IO{static const int S=1<<21;char buf[S],*p1,*p2;int st[105],Top;~IO(){clear();}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}IO&operator>>(char&x){while(x=gc(),x==' '||x=='\n');return*this;}template<typename T>IO&operator>>(T&x){x=0;bool f=0;char ch=gc();while(ch<'0'||ch>'9'){if(ch=='-')f^=1;ch=gc();}while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=gc();f?x=-x:0;return*this;}IO&operator<<(const char c){pc(c);return*this;}template<typename T>IO&operator<<(T x){if(x<0)pc('-'),x=-x;do{st[++st[0]]=x%10,x/=10;}while(x);while(st[0]){pc('0'+st[st[0]--]);}return*this;}}ld;
} using namespace fast_IO;
#define ll long long
#define ull unsigned long long
#define REP(i, l, r) for (int i = l; i <= r; ++i)
#define PER(i, l, r) for (int i = l; i >= r; --i)
#define rep(i, l, r) for (int i = l; i < r ; ++i)
#define per(i, l, r) for (int i = l; i > r ; --i)
namespace RPD {
    #define pf(x) ((x) * (x))
    #define ppf(x) ((x) * (x) * (x))
    #define modf(x, mod) (((x) % mod + mod) % mod)
    #define min3(x, y, z) (min(x, min(y, z)))
    #define min4(x, y, z, w) (min(min(x, y), min(z, w)))
    #define max3(x, y, z) (max(x, max(y, z)))
    #define max4(x, y, z, w) (max(max(x, y), max(z, w)))
    #define gmin(x, y) (x = min(x, y))
    #define gmax(x, y) (x = max(x, y))
    #define lowbit(x) (x & -x) 
    #define bitcount(x) __builtin_popcount(x)
    #define albit(x) ((1 << (x)) - 1)
    #define mkbit(x) (1 << (x))
    #define gtbit(x, id) (((x) >> (id)) & 1)
}
// #define ld cin
// #define jyt cout
//#define int long long
const int N = 1200 + 7;
const int inf = 1e9 + 7;
const ll linf = 1e18 + 7;
const int P = 1e9 + 7;
namespace MG42 {
    int n, A[N];
    inline ll qpow(ll x, ll y) {
        ll Res = 1; 
        while (y) {
            if (y & 1) (Res *= x) %= P;
            y >>= 1, (x *= x) %= P;
        } return Res;
    }
    ll f[N][N], g[N], fac[N], inv[N];
    inline ll C(ll x, ll y) {
        if (x < y || x < 0 || y < 0) return 0;
        return fac[x] * inv[y] % P * inv[x - y] % P;
    }
    signed main() { fac[0] = inv[0] = 1;
        ld >> n; int x = 0, Cnt0 = 0, Cnt1 = 0;
        REP(i, 1, n) ld >> x, A[x] = true;
        REP(i, 1, 2 * n) fac[i] = fac[i - 1] * i % P, inv[i] = qpow(fac[i], P - 2); 
        g[0] = 1;
        REP(i, 1, 2 * n) 
            REP(j, 1, i) 
                (g[i] += g[i - j] * g[j - 1] % P * ((i - j + 1) + 1) % P * C(i - 1, j - 1) % P) %= P;
        // cerr << g[3] << '\n';
        f[2 * n + 1][0] = 1;
        PER(i, 2 * n, 1) {
            if (!A[i]) {
                REP(j, Cnt0 + 1, Cnt1) f[i][j] = f[i + 1][j] * (2 * j - j - Cnt0) % P;
            } else {
                REP(j, Cnt0, Cnt1) f[i][j] = f[i + 1][j];
                REP(j, Cnt0, Cnt1) 
                    REP(k, j + 1, Cnt1 + 1) 
                        (f[i][k] += f[i + 1][j] * C(Cnt1 - j, k - j - 1) % P * g[k - j - 1] % P * (k - j + 1) % P) %= P;
            } Cnt0 += (A[i] == 0), Cnt1 += (A[i] == 1);
        }
        // cerr << f[6][1] << '\n';
        ll Ans = f[1][n] * qpow(qpow(2, n), P - 2) % P;
        jyt << Ans << '\n';
        return 0; 
    }
}
signed main() {
    // freopen("copy.in", "r", stdin);
    // freopen("copy.out", "w", stdout);
//  ios::sync_with_stdio(false);
//  cin.tie(0), cout.tie(0);
    MG42::main();
    return 0;
}
posted @ 2025-02-11 13:46  flowing_boat  阅读(32)  评论(0)    收藏  举报