zoukankan      html  css  js  c++  java
  • 【DP】那一天我们许下约定

    题目描述

    那一天我们在教室里许下约定。
    我至今还记得我们许下约定时的欢声笑语。我记得她说过她喜欢吃饼干,很在意自己体重的同时又控制不住自己。她跟我做好了约定:我拿走她所有的饼干共N块,在从今天起不超过D天的时间里把所有的饼干分次给她,每天给她的饼干数要少于M以防止她吃太多。
    当然,我们的约定并不是饼干的约定,而是一些不可言状之物。
    现今回想这些,我突然想知道,有多少种方案来把饼干分给我的她。

    输入格式

    每个测试点有多组测试数据。数据组数T10
    对于每组数据,有一行共三个整数N,D,MN,D,MN,D,M含义如题。
    输入结束标识为 “0 0 0” (不含引号)。

    输出格式

    对于每组数据,输出一行共一个整数,表示方案数对 998244353 取膜后的结果。


    背景

      关于这道题,是昨天的考试,至于考试为什么没有打,就应该是睡过头的锅了。

      但是让我打我也不会啊 【滑稽

      其实我一开始就打了一个n3的Floyed,发现20分,我也没管,觉得暴力20分挺正常。【这里有Floyed判最小环的题及讲解--->戳这里呢

      但是优化以后就全wa了,检查之后发现反了一个很奇怪的错误。


    思路分析

      我们来观察一下题面,其中有一句话。

    我拿走她所有的饼干共N块,在从今天起不超过D天的时间里把所有的饼干分次给她,每天给她的饼干数要少于M以防止她吃太多。

      首先,我们会发现这种方案数之间,可能是有一些递推关系的。

      所以采取DP的做法。

      其次我们会发现一个性质:

          这个人为了减肥,在少吃,最多能吃n天。

      所以假设

      那么很显然

      所以这是要打一个n3的暴力,然后像我一样的WAWAWAWA?

      显然不是,2000的3次方是绝对会死的。  

      那么我们来考虑一下怎么优化。

      究竟有什么地方是可以被省略的呢?

      for (i)

        for(j)

          for(k) ???????????

      感觉没毛病 啊,没什么多余的????????

      开始陷入迷茫,算了算了,假算法,弃了弃了?

      ?????????????????????

      怎么能这样?

      不就是需要把中间的某一位省掉嘛?这还不简单?

      第一维是天数,不能动。

      第二维是饼干数,也不能动。

      想也不用想,铁定省去第三维。

      ?仔细想想,对于 f [ i-1][...] 求和,你想到了什么?  

      前缀和啊,来了来了。

      所以我们在循环中维护一个前缀和,就可以 O(1)的转移了。

      

      这题的大思路就是这样。


    细节坑点  

      其实上面的部分很多人都想到了【当然不包括我

      但是却AC不能,这是为什么呢?

      其实就一个,边界问题

      看到这里就先别往下翻,先对着自己的代码好好想想每一重循环的边界。

      然后我们继续说。

      首先是第一重:

          循环边界 1~n ? 1~d ? 

          刚刚说过,最多只能是n天,但是实际上n与d的关系是不确定的。

          所以此时边界应该是 1~min(n,d)

      剩下的先别看。

      然后是第二重:

          循环边界 1~n ?

          不是吗?开始疑神疑鬼? 莫非是 0~n? i~n?

          交一交,发现都是对的。

          ???我来解释一下这是为什么。

          原因就是组合数Cqi在q小于i的情况下,值为0

          ??????那 1~min(i*m-i,n) 行不行啊 ?

          然后你就发现wa了。

          ???每天最多吃(m-1)个,吃 i 天没毛病啊?

          我还没理解为什么如果可以解释请留言

          对不起是由于sum转移的问题,是我傻了

      然后是一个小小的优化:

        if(!d||(n/d>=m)||(m==1)) {
                cout<<"0"<<endl;
                continue;
            }

     刚刚还有一个人问我为什么前缀和要减到 sum[ i-1] [j-m] 而不是 sum[ i-1][ j-m+1]

      我只想说看看题好嘛?每天吃少于m的,是吃不到m个的,所以你的 j-m+1实际上就是j-m


    代码来了

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    
    const long long mod=998244353;
    long long d,jc[2010],ny[2010],fm[2010];
    long long ans,n,m,sum[2010][2010];
    long long f[2010][2010];
    
    long long ks(long long x,long long k) {
    	long long num=1;
    	while(k){
    		if(k&1)num=num*x%mod;
    		x=x*x%mod;
    		k>>=1;
    	}
    	return num;
    }
    
    int main()
    {
    	while(scanf("%lld%lld%lld",&n,&d,&m)&&n) {
    		memset(f,0,sizeof f);
    		ans=0;
    		memset(ny,0,sizeof ny);
    		memset(jc,0,sizeof jc);
    		memset(fm,0,sizeof fm);
    		fm[1]=d%mod;
    		for(int i=2;i<=n;i++)
    			fm[i]=(fm[i-1]*((d-i+1)%mod))%mod;
    		jc[1]=1;
    		for(int i=2;i<=n;i++)
    			jc[i]=(jc[i-1]*i)%mod;
    		ny[n]=ks(jc[n],mod-2);
    		for(int i=n-1;i>=1;i--)
    			ny[i]=(ny[i+1]*(i+1))%mod;
    		f[0][0]=1;
    		for(int i=0;i<=n;i++)
    			sum[0][i]=1;
    		for(int i=1;i<=min(n,d);i++) {
    			for(int j=1;j<=n;j++) {
    				if(j-m+1>0)
    					f[i][j]=((sum[i-1][j-1]-sum[i-1][j-m])%mod+mod)%mod;
    				else 
    					f[i][j]=sum[i-1][j-1]%mod;
    				sum[i][j]=(sum[i][j-1]+f[i][j])%mod;
    			}
    			ans=(ans+((f[i][n]*ny[i]%mod)*fm[i]%mod)%mod)%mod;
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    过河问题 (Standard IO)
    单词分类 (Standard IO)
    C#综合揭秘——细说多线程(上)
    使用NPOI导入导出标准Excel
    C# 转义字符 ''反斜杠
    ref和out的区别
    抽象类接口的区别
    方法签名
    SQL Server的通用分页存储过程 未使用游标,速度更快!
    SQL Server存储过程Return、output参数及使用技巧
  • 原文地址:https://www.cnblogs.com/qxyzili--24/p/11218846.html
Copyright © 2011-2022 走看看