【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\n",ans);
	}
	return 0;
}

  

posted @ 2019-07-20 19:04  鸽子咕  阅读(156)  评论(0编辑  收藏  举报