[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\)。
合法的状态数为 \(\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;
}