cf#319-div2-B. Modulo Sum-(dp) 求模

博客内容讲述了如何解决寻找序列中子集和为给定数的倍数问题,利用动态规划dp数组进行状态转移,确保在递推过程中不破坏原有状态,并通过tmp_dp数组避免递归中的影响。在找到和为模数倍的子集时,直接结束以避免超时。
摘要由CSDN通过智能技术生成

题意要n个数中 看能否选出一个组合,使其之和为 m的倍数

开一个dp[1001]、tmp_dp[1001]

dp[i]的意思,是能得到 一个组合,其和为sum 且sum%m==i 那么我们就让dp[i]=1;表示存在

在递推的过程中,如果我们在前i个数得到dp[j]=1;也就是存在sum1%m==j;

那么对于a[i+1]、我们可以推得  一个sum2=sum1+a[i+1]、所以得到sum2%m==k; 也就是dp[k]=1;

即: 由在前i次循环中,如果得到dp[j]=1;可以在第i+1次循环得到  dp[   ( j+a[i+1] )%m   ]=1;

这里为什么强调前i次循环和第i+1次循环? 因为要得到dp[k]=1,其条件中的dp[j]=1;必须在 a[i+1]的递推之前就已经得到

这里给个反例,  a[1]=1,m=100如果我从a[1]=1;推得dp[1%100]=dp[1]=1; 然后我又根据这个dp[1]=1得到dp[(1+a[1])%100]=dp[2]=1;这样就一直推下去了

所以我们在从dp[j]推到dp[k]的时候,dp[k]应该记录在一个tmp_dp[k]数组里(为了不在递归中影响dp数组的值)

做完整个递推后,才把tmp_dp[]的值赋给dp;


还有一点是dp[0]就意味有 sum%m==0,也就是答案找到了,此时直接break,否则超时;


#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <queue>
#include <list>
#include <set>
#include <vector>
using namespace std;


int dp[1005];
int tm[1000005];
int tmp_dp[1005];//主函数中解释;
int main()
{
	
	int n,m;
	int i,j;
	scanf("%d%d",&n,&m);
	
	for (i=1;i<=n;i++)
		scanf("%d",&tm[i]);
 

	for (i=1;i<=n;i++)
	{

		if (dp[0]) break;
		for (j=1;j<m;j++)
		{
			if (dp[j])//此处dp[j]为上次循环所留下的结果 
			{
			 
				tmp_dp[(j+tm[i])%m]=1;//如果直接改dp数组的值,上面的判断会出问题,会出现利用"自己得到自己的两倍"这样的情况				 
			}//此处意思是,如果dp[j]存在,即能组成 一个sum,使得sum%m=j,那么sum+tm[i]得到sum2,所以sum2%m也是可以得到的*最终目标是求sumX%m==0
		//	也就是有整除m的数存在就可以停止循环了,算是有点贪心吧。

		}
		tmp_dp[tm[i]%m]=1; 
		for (j=0;j<m;j++)
		dp[j]=tmp_dp[j];
	}
	
	if (dp[0])
		printf("YES\n");
	else
		printf("NO\n"); 
	return 0;
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值