LOJ#2304. 「NOI2017」泳池(70pts) dp
满分做法需要用到很神仙的优化方式,这里只给出 部分分的做法.
想出了 70pts 做法还是十分开心的.
开始的时候先想了一个 35 pts 做法:
考虑连续段,那么连续段的长度不会超过 $K$,高度不会超过 $K$,所以 $K \leqslant 9$ 的状态都能搜出来.
然后令 $f[i]$ 表示固定 $K$ 时,$1$ ~ $i$ 的答案.
最后输出 $f[n] (K)-f[n](K-1)$ 即可.
然后 70pts 的话就是用 dp 来替换爆搜.
令 $sum[i][j]$ 表示长度为 $i$,高度的最小值大于等于 $j$ 的连续段的概率和.
然后转移的话和积劳成疾类似,即枚举最小值第一次出现的位置.
这道题调了大概 20 min,有两处错误:
1. 把 sum[i][1] 写成 sum[i][K]
2. 有一个地方忘记取模了.
虽说错误不多,但是像第二种错误不能再犯了,因为这种计数题根本没法调.
code:
#include <bits/stdc++.h> #define N 1006 #define ll long long #define mod 998244353 #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n,q,dp[N],sum[N][N]; int qpow(int x,int y) { int tmp=1; for(;y;y>>=1,x=(ll)x*x%mod) if(y&1) tmp=(ll)tmp*x%mod; return tmp; } int INV(int x) { return qpow(x,mod-2); } int calc(int K) { int x,y,z; memset(dp,0,sizeof(dp)),memset(sum,0,sizeof(sum)); for(int i=0;i<N;++i) sum[0][i]=1; for(int i=1;i<=K;++i) for(int j=K/i;j>=1;--j) { sum[i][j]=sum[i][j+1]; z=(ll)qpow(q,j)*(1-q+mod)%mod; for(int p=1;p<=i;++p) (sum[i][j]+=(ll)z*sum[p-1][j+1]%mod*sum[i-p][j]%mod)%=mod; } dp[0]=1; for(int i=1;i<=n;++i) { dp[i]=sum[i][1]; for(int j=1;j<=i;++j) (dp[i]+=(ll)(1-q+mod)%mod*sum[i-j][1]%mod*dp[j-1]%mod)%=mod; } return dp[n]; } int main() { // setIO("input"); int K,x,y,z; scanf("%d%d%d%d",&n,&K,&x,&y),q=(ll)x*INV(y)%mod; if(n==1) printf("%d\n",(ll)qpow(q,K)*(1-q+mod)%mod); else printf("%d\n",(ll)(calc(K)-calc(K-1)+mod)%mod); return 0; }