zoukankan      html  css  js  c++  java
  • [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++

  • 相关阅读:
    CSS浮动(float、clear)通俗讲解
    JAVA 类的加载
    数据库操作 delete和truncate的区别
    正则表达式 匹配相同数字
    Oracle EBS OM 取消订单
    Oracle EBS OM 取消订单行
    Oracle EBS OM 已存在的OM订单增加物料
    Oracle EBS OM 创建订单
    Oracle EBS INV 创建物料搬运单头
    Oracle EBS INV 创建物料搬运单
  • 原文地址:https://www.cnblogs.com/wzc521/p/11218790.html
Copyright © 2011-2022 走看看