[NOI2017] 泳池

tag: 概率期望,dp,线性递推

一眼不可做,然后跳过

第一眼肯定枚举矩形,然后计算,然后发现十分不可做……因为要使你枚举的矩形最大而没有比它更大的,这个不太好用具体式子描述。

考虑转化为求 \([S\leq k]-[S\leq k-1]\),转化为所有矩形 $\leq k $,感觉可做一点了。

由于这道题只与长度有关系,设 \(f_i\) 表示底边为 \(i\) 的概率,转移的话就往后面拼一段联通块上去。

\(f_{i+j+1}=f_i+g_j\cdot(1-q)\),\(g_j\) 为概率,\(1-q\)是因为中间要空一列。

也就是说 \(f_i=\sum_{j=0}^k f_{i-j-1}\cdot g_j\cdot(1-q)\),看一眼数据范围矩阵快速幂 \(O(k^3logn)\) 是不行的,但是 \(O(k^2logn)\) 的常系数线性递推是可行的,所以问题转化为求 \(g_i\)。

对于一个联通块,可以看作是很多个矩形的并,但是仅仅 \(i\) 一维显然无法 \(dp\),所以考虑加一维 \(j\),设 \(h_{i,j}\) 表示最宽的那个矩形底边 \(=i\),高度为 \(j\)。

也就是说在高度为 \(j+1\) 的一行有至少一个危险区域,于是枚举第一个危险区域的位置 \(bp\) (假设当前区域为 \([1,i]\)),那么 \([1,bp-1]\) 这一段矩形的高度是大于 \(j\) 的(因为 \((bp,j+1)\) 是第一个危险区域),\([bp+1,i]\) 这一段的矩形则可以等于 \(j\)。

\[h_{i,j}=q^j(1-q)\sum_{bp=1}^i(\sum_{p>j}h_{bp-1,p})\cdot(\sum_{p\geq j}h_{i-bp,p})\quad\quad(i\cdot j\leq k) \]

合法的状态数为 \(\sum_i\left\lfloor\frac ki\right\rfloor=klnk\),一次转移复杂度 \(O(k)\) (用前缀和优化),于是计算 \(h_{i,j}\) 的复杂度为 \(O(k^2lnk)\)

总复杂度\(O(k^2logk-k^2logn)\)

#include<bits/stdc++.h>
using namespace std;

template<typename T>
inline void Read(T &n){
	char ch; bool flag=0;
	while(!isdigit(ch=getchar()))if(ch=='-')flag=1;
	for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
	if(flag)n=-n;
}

enum{
    MAXN = 1005,
    MOD = 998244353
};

inline int inc(int a, int b){
    a += b;
    if(a>=MOD) a -= MOD;
    return a;
}

inline void iinc(int &a, int b){a = inc(a,b);}

inline int dec(int a, int b){
    a -= b;
    if(a<0) a += MOD;
    return a;
}

inline void ddec(int &a, int b){a = dec(a,b);}

inline int ksm(int base, int k=MOD-2){
    int res = 1;
    while(k){
        if(k&1)
            res = 1ll*res*base%MOD;
        base = 1ll*base*base%MOD;
        k >>= 1;
    }
    return res;
}

int h[MAXN][MAXN], g[MAXN], S[MAXN][MAXN], f[MAXN];

int Pow[MAXN], n, k;

int F[MAXN<<2], G[MAXN<<2], Tmp[MAXN<<2];
inline int Solve(int n, int k){
    for(register int i=1; i<=k+1; i++) S[0][i] = 1; g[0] = 1;
    for(register int i=1; i<=k; i++){
        S[i][k/i+1] = 0;
        for(register int j=k/i; j; j--){
            int tmp = 0;
            for(register int bp=1; bp<=i; bp++)
                tmp = (tmp+1ll*S[bp-1][j+1]*S[i-bp][j])%MOD;
            h[i][j] = 1ll*tmp*Pow[j]%MOD*dec(1,Pow[1])%MOD;
            S[i][j] = inc(S[i][j+1],h[i][j]);
        }
        g[i] = S[i][1];
    }
    f[0] = 1;
    for(register int i=1; i<=k; i++){
        f[i] = g[i];
        for(register int j=0; j<=k and i-j-1>=0; j++) 
            f[i] = (f[i]+1ll*f[i-j-1]*g[j]%MOD*dec(1,Pow[1]))%MOD;
    }
    if(n<=k) return f[n];

    for(register int i=0; i<=k; i++) g[i] = 1ll*g[i]*dec(1,Pow[1])%MOD;
    memset(F,0,sizeof F); memset(G,0,sizeof G);
    F[1] = 1; G[0] = 1;
    while(n){
        if(n&1){
            for(register int i=0; i<=k; i++) for(register int j=0; j<=k; j++) Tmp[i+j] = (Tmp[i+j]+1ll*F[i]*G[j])%MOD;
            for(register int i=k+k; i>k; i--) if(Tmp[i]) for(register int j=0; j<=k; j++) Tmp[i-j-1] = (Tmp[i-j-1]+1ll*Tmp[i]*g[j])%MOD;
            for(register int i=0; i<=k; i++) G[i] = Tmp[i];
            for(register int i=0; i<=k+k; i++) Tmp[i] = 0;
        }
        for(register int i=0; i<=k; i++) for(register int j=0; j<=k; j++) Tmp[i+j] = (Tmp[i+j]+1ll*F[i]*F[j])%MOD;
        for(register int i=k+k; i>k; i--) if(Tmp[i]) for(register int j=0; j<=k; j++) Tmp[i-j-1] = (Tmp[i-j-1]+1ll*Tmp[i]*g[j])%MOD;
        for(register int i=0; i<=k; i++) F[i] = Tmp[i];
        for(register int i=0; i<=k+k; i++) Tmp[i] = 0;
        n >>= 1;
    }

    int ans=0; for(register int i=0; i<=k; i++) ans = (ans+1ll*G[i]*f[i])%MOD;

    return ans;
}

int main(){
    Read(n); Read(k); int tmpx, tmpy; Read(tmpx); Read(tmpy);
    Pow[0] = 1; Pow[1] = 1ll*tmpx*ksm(tmpy)%MOD; for(register int i=2; i<=k; i++) Pow[i] = 1ll*Pow[i-1]*Pow[1]%MOD;
    printf("%d\n",dec(Solve(n,k),Solve(n,k-1)));
    return 0;
}
上一篇:树莓派用python处理mpu6500数据


下一篇:常见初等代数公式