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;
}

浙公网安备 33010602011771号