【牛客】暑假多校训练营4 J-Qu‘est-ce Que C‘est? 题解

传送门:Qu’est-ce Que C’est?
标签:动态规划

题目大意

给出两个正整数n和m,问你能构造出多少种不同的序列,符合以下两个条件:1、序列中所有数都在区间[-m,m]中;2、对于任意1<=l<r<=n,都满足al+al+1+······+ar-1+ar>=0。最终得到的答案对998244353取模。
输入:两个正整数n,m(1<=n,m<=5000)。
输出:一个非负整数代表对998244353取模的结果。

算法分析

  • 一眼丁真,鉴定为简单的dp。数据范围不超过5000,那只能是O(n2)做法带个小常数。我们先考虑如何设计状态,既然5000×5000的数组开不下,我们就开个滚动数组2×5000。对于序列中每个位置放的数,可以分为两种情况讨论:假设在i位前已经有不合法的负数子段,那么这一位就没有意义了;如果从第1到第i-1位都满足条件(所有不包括第i位的子段都为非负数),那么只需确保包括i的子段和不为负数即可。
  • 显而易见的是,包括i的子段要么从第i-1位转移而来,要么以i这一位单独作为一个子段。在这种情况下,我们只需记录以第i-1位结尾的子段中最小的那个,只要第i个元素能使最小的子段满足条件,那其它的子段也就没问题了。不过题目不是让我们构造出一个满足条件的序列,而是让我们算出所有的序列有多少种。这种情况下我们设计的状态就要把下标和值互换,也就是说:dp[i][j]代表以第i位结尾的子段中最小值为j的构造方案数。
  • 考虑到j可能为负数,我们可以开一个unordered_map,也可以把数组扩大一点,以[m+1,2m]的区间代替[-m,-1],这样一来转移复杂度就能稳定在O(1)。我们再考虑转移的方式。这里要分三种情况讨论:1、j为负数时,其值可以由所有非负数状态转移而来。因为题中规定l和r不能相等,那么负数时肯定不能连续出现的,且要保证它不会把前面非负数的子段变成负数。2、j为0时,其值可以由[-m,m]中所有状态转移而来。不管前面子段的最小值为多少,我们都可以将其变为0:如果是负数则加上一个正数,否则加上一个0。3、j为正数时,其值可以由所有非负数状态和部分负数状态转移而来。
  • 第三种转移可能不太好理解,这里给出详细的思路:对于非负数:如果是小于或等于j的数,可以加上一个不大于m的数使其等于j;如果是大于j的数,我们可以放一个j在第i位使其最小值更新为j。对于负数:我们能加的正数最大为m,那么只有大于等于j-m的负数可以转移过来。至此本题已经结束了,你可能会认为时间复杂度是O(nm2),但我们其实可以通过一个前缀和的操作使算法总复杂度优化到O(nm)。

代码实现

#include <iostream>
using namespace std;
#include <vector>
const long long mod=998244353;
long long dp[2][10005];
int main(){
	int i,m,i0,i1,j,x,mw,n;
	long long ans=0LL;
	cin>>n>>m;
	for(i=1;i<=n;i++){
		i1=i&1;
		i0=(!i1);
		for(j=0;j<=2*m;j++)
			dp[i1][j]=0;
		for(j=m+1;j<=2*m;j++)
			dp[i1][j]=(dp[i0][m]-dp[i0][j-m-1]+mod+dp[i1][j-1])%mod;
		dp[i1][0]=(dp[i0][2*m]+dp[i0][m])%mod;
		for(j=1;j<m;j++)
			dp[i1][j]=(dp[i0][m]+dp[i0][2*m-j]+dp[i1][j-1])%mod;
		dp[i1][j]=(dp[i0][j]+dp[i1][j-1])%mod;
		if(i==1){
			for(j=0;j<=m;j++)
				dp[i1][j]=j+1;
			for(j=m+1;j<=2*m;j++)
				dp[i1][j]=j-m;
		}
	}
	cout<<(dp[i1][m]+dp[i1][2*m])%mod;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值