• [CSP-S模拟测试]:那一天我们许下约定(DP+组合数学)


    题目传送门(内部题2)


    输入格式

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


    输出格式

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


    样例

    样例输入:

    5 2 5
    3 3 3
    5 4 5
    4 1 2
    1 5 1
    1250 50 50
    0 0 0

    样例输出:

    4
    7
    52
    0
    0505279299


    数据范围与提示

    $T leqslant 10$

    对于$30\%$的数据:

    $N leqslant 20$

    $D leqslant 20$

    $M leqslant 10$

    对于$100\%$的数据:

    $N leqslant 2000$

    $D leqslant {10}^{12}$

    $M leqslant 2000$


    题解

    $30\%$算法:

    要注意每天给她的饼干数要少于M,没有等于。

    看到这道题,首先应该想到DP,定义dp[i][j]表示到第i天,还剩j个饼干的方案数。

    那么很轻易的就可以列出状态转移方程:

    $dp[i][j]= sum limits_{k=0}^{ min(M-1,N-j)} dp[i-1][j+K]$

    时间复杂度:$O(N imes D imes M)$。

    空间复杂度:$D imes N$。

    期望得分:$30$分。

    实际得分:$30$分。

    $30\%$算法(进阶):

    上面的算法显然空间不能接受,那么我们应该怎么优化呢?

    发现$D$很大,但是我们又发现真正会给她饼干之多$N$天。

    那么我们就相当与将天数压缩到$N$天,显然在空间上就可以接受了。

    定义$dp[i][j]$表示真的给她饼干的天数为$i$,一共给出了$j$块饼干的方案数。

    那么就又可以列出状态转移方程了:

    $dp[i][j]= sum limits_{k=max(j-M+1,0)}^{j-1} dp[i-1][K]$

    答案即为:$ans= sum limits_{i=1}^{N} dp[i][N] imes C_D^i$。

    至于如何计算$C_D^i$:

    显然杨辉三角打表无论是时间上还是空间上都不能接受,$Lucas$定理时间上也不能够接受,所以这两种常用的方式显然都行不通,所以我们考虑化简式子:

    $C_D^i = frac{D!}{i! imes (D-i)!} = frac {D-i+1 imes D-i+2 imes ... imes D-1 imes D}{1 imes 2 imes ... imes i-1 imes i}$

    虽然$D$很大,但是$i leqslant N$所以我们只需要计算很小的一段区间即可,无论是时间上还是空间上都的到了解决。

    时间复杂度:$O(N^2 imes M)$。

    空间复杂度:$N^2$。

    期望得分:$30$分。

    $100\%$算法:

    发现上面$30\%$(进阶)的算法中,枚举K的循环可以使用前缀和优化实现$O(1)$转移。

    时间复杂度:$O(N^2)$。

    空间复杂度:$N^2$。

    期望得分:100分。


    代码时刻

    $30\%$代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    long long d;
    long long dp[2010][2010];//数组不要过大
    int main()
    {
    	while(1)
    	{
    		scanf("%d%lld%d",&n,&d,&m);
    		if(!n&&!m&&!d)break;
    		memset(dp,0,sizeof(dp));
    		dp[0][n]=1;
    		for(int i=1;i<=d;i++)
    			for(int j=0;j<=n;j++)
    				for(int k=0;k<m&&j+k<=n;k++)
    					dp[i][j]=(dp[i][j]+dp[i-1][j+k])%998244353;//状态转移
    		printf("%lld
    ",dp[d][0]);
    	}
    	return 0;
    }
    

    $30\%$算法(进阶):

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    long long d;
    long long dp[2010][2010];
    long long ans;
    long long p[2010],jc[2010],qsm[2010],c[2010];
    long long qpow(long long x,long long y)
    {
    	long long ans=1;
    	while(y)
    	{
    		if(y&1)ans=(ans*x)%998244353;
    		y>>=1;
    		x=(x*x)%998244353;
    	}
    	return ans;
    }
    void pre_work_wzc()//预处理
    {
    	jc[0]=1;
    	for(int i=1;i<=2000;i++)
    		jc[i]=jc[i-1]*i%998244353;
    	for(int i=0;i<=2000;i++)
    		qsm[i]=qpow(jc[i],998244351)%998244353;
    }
    void pre_work()//还是预处理
    {
    	memset(dp,0,sizeof(dp));
    	dp[0][0]=1;
    	ans=0;
    	p[0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		p[i]=(d-i+998244354)%998244353*p[i-1]%998244353;
    		c[i]=p[i]*qsm[i]%998244353;
    	}
    }
    int main()
    {
    	pre_work_wzc();
    	while(1)
    	{
    		scanf("%d%lld%d",&n,&d,&m);
    		if(!n&&!m&&!d)break;
    		pre_work();
    		for(int i=1;i<=min((long long)n,d);i++)
    			for(int j=i;j<=n;j++)
    				for(int k=max(j-m+1,0);k<j;k++)
    					dp[i][j]=(dp[i][j]+dp[i-1][k])%998244353;//状态转移
    		for(int i=1;i<=n;i++)
    			ans=(ans+dp[i][n]*c[i])%998244353;//统计答案
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    

    $100\%$算法:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    long long d;
    long long dp[2010][2010];
    long long flag[2010][2010];//前缀和数组
    long long ans;
    long long p[2010],jc[2010],qsm[2010],c[2010];
    long long qpow(long long x,long long y)
    {
    	long long ans=1;
    	while(y)
    	{
    		if(y&1)ans=(ans*x)%998244353;
    		y>>=1;
    		x=(x*x)%998244353;
    	}
    	return ans;
    }
    void pre_work_wzc()
    {
    	jc[0]=1;
    	for(int i=1;i<=2000;i++)
    		jc[i]=jc[i-1]*i%998244353;
    	for(int i=0;i<=2000;i++)
    		qsm[i]=qpow(jc[i],998244351)%998244353;
    }
    void pre_work()
    {
    	memset(dp,0,sizeof(dp));
    	ans=0;
    	p[0]=1;
    	for(int i=1;i<m;i++)
    		dp[1][i]=1;
    	for(int i=1;i<=n;i++)
    	{
    		p[i]=(d-i+998244354)%998244353*p[i-1]%998244353;
    		c[i]=p[i]*qsm[i]%998244353;
    		flag[1][i]=flag[1][i-1]+dp[1][i];
    	}
    }
    int main()
    {
    	pre_work_wzc();
    	while(1)
    	{
    		scanf("%d%lld%d",&n,&d,&m);
    		if(!n&&!m&&!d)break;
    		pre_work();
    		for(int i=2;i<=min((long long)n,d);i++)
    			for(int j=i;j<=n;j++)
    			{
    				dp[i][j]=(flag[i-1][j-1]-flag[i-1][max(j-m,0)]+998244353)%998244353;
    				flag[i][j]=(flag[i][j-1]+dp[i][j])%998244353;
    			}
    		for(int i=1;i<=n;i++)
    			ans=(ans+dp[i][n]*c[i])%998244353;
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

    rp++

  • 相关阅读:
    (转)CTP: 平昨仓与平今仓,log轻轻告诉你.......
    linux上的语音识别程序
    6个可以隐藏运行bat,浏览器等程序的方法
    Android——用PagerAdapter实现View滑动效果
    Android——关于PagerAdapter的使用方法的总结(转)
    Android——浅谈HTTP中Get与Post的区别(转)
    Android——远程存储器存储:JDK方式和Volley框架的get和post
    android——字符串string(转)
    Android——事务
    Android——监听事件总结
  • 原文地址:https://www.cnblogs.com/wzc521/p/11218790.html
Copyright © 2020-2023  润新知