解题思路:
先来说一下题意,n个数,每个数前面随意加上+-号(当然+号等于没加),然后所有的数相加,判断和是否为k的整数。
两种方法,一个是使用dfs深搜,将每种情况都进行尝试,时间复杂度2^n,能过本网站数据,数据量大的时候会超时。
还有一种是dp,这是最好的方法。
dfs代码:
#include <bits/stdc++.h>
using namespace std;
int p[10007];
int n,m,vis=0;
void dfs(int k) {
if(k==n) {//全部更改过位置
int z=0;
for(int i=0; i<n; i++) {
z+=p[i];
}
if(z%m==0)vis=1;//如果可以整除
return;
} else {
for(int i=0; i<2; i++) {
if(i&1) {
dfs(k+1);
} else {
p[k]*=-1;
dfs(k+1);
p[k]*=-1;
}
}
}
}
int main () {
cin>>n>>m;
for(int i=0; i<n; i++) {
cin>>p[i];
}
dfs(0);
cout<<(vis?"YES":"NO");
return 0;
}来详细说一下dp代码,dp[i][j]为前i个数是否可以被j整除,1表示可以,0不可以。
因为最后所有的值是要相加在一起的,所以最后的值依赖于前面的情况。
以第二个数为例,如果想要第一个数加上第二个数后sum对k取余值为j,那么第一个数p1只有两种情况,一个是p1==j-a2,一个是p1==j+a2(这里对p2前加(-)号)
推广到所有的数身上,想要加到第i个数sum%k的值为j,那么加到第i-1个数时必须满足两种情况(j-pi或者j+pi)中的一个,这样在加上第i个数或减去第i个数后和sum才为j,j%k=j(j<k)。
根据(a+b)%k=(a%k+b%k)%k,可以先进行取余后处理,一方面可以减少数组开销另一方面确保j<k。
这样我们就可以写递推式了,dp[i][j]代表的是前i个数是否可以被j整除,dp[i][j]就和(dp[i-1][(j+p[i])%k]、dp[i-1][(j-p[i])%k])有关了,只有这两个中的一个为真,dp[i][j]才为真。
递推式:dp[i][j]=dp[i-1][(j+p[i]+k)%k] || dp[i-1][(j-p[i]+k)%k]
两个做或运算,有真则真,注意这里分别+k,是为了防止j-p[i]为数组下标为负数的情况。
根据同余 (j-p[i]+k)%k=(j-p[i])%k+k%k=(j-p[i])%k
dp代码:
#include <bits/stdc++.h>
using namespace std;
int p[10007];
bool dp[10007][107];
int n,k;
int main () {
cin>>n>>k;
for(int i=1; i<=n; i++) {
cin>>p[i];
p[i]%=k;
}
dp[0][0]=1;//边界,记为1
for(int i=1; i<=n; i++) {
for(int j=0; j<k; j++) {
dp[i][j]=dp[i-1][(j+p[i]+k)%k]||dp[i-1][(j-p[i]+k)%k];
}
}
cout<<(dp[n][0]?"YES":"NO");//dp[n][0]表示前n个数相加,对k取余余数为0,即:前n个数相加有可以整除k的情况
return 0;
}dp的核心是dp[i][j]为前i个数是否可以被j整除,这是本题的核心。
以上。
0.0分
1 人评分
C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:
一点编程也不会写的:零基础C语言学练课程
解决困扰你多年的C语言疑难杂症特性的C语言进阶课程
从零到写出一个爬虫的Python编程课程
只会语法写不出代码?手把手带你写100个编程真题的编程百练课程
信息学奥赛或C++选手的 必学C++课程
蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程
手把手讲解近五年真题的蓝桥杯辅导课程
发表评论 取消回复