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
",(ll)qpow(q,K)*(1-q+mod)%mod);            
    else printf("%d
",(ll)(calc(K)-calc(K-1)+mod)%mod); 
    return 0;         
}

  

原文地址:https://www.cnblogs.com/guangheli/p/13083995.html