蓝桥杯 倍数问题【第九届】【省赛】【A组】dp/背包问题

资源限制

内存限制:256.0MB   C/C++时间限制:1.0s   Java时间限制:3.0s   Python时间限制:5.0s

问题描述

  众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。现在小葱给了你 n 个数,希望你从这 n 个数中找到三个数,使得这三个数的和是 K 的倍数,且这个和最大。数据保证一定有解。

输入格式

  从标准输入读入数据。

  第一行包括 2 个正整数 n, K。
  第二行 n 个正整数,代表给定的 n 个数。

输出格式

  输出到标准输出。
  输出一行一个整数代表所求的和。

样例入

  4 3
  1 2 3 4

样例输出

9

样例说明

  选择2、3、4。

数据约定

  对于 30% 的数据,n <= 100。
  对于 60% 的数据,n <= 1000。
  对于另外 20% 的数据,K <= 10。
  对于 100% 的数据,1 <= n <= 10^5, 1 <= K <= 10^3,给定的 n 个数均不超过 10^8。


  资源约定:
  峰值内存消耗(含虚拟机) < 256M
  CPU消耗 < 1000ms


  请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

  注意:
  main函数需要返回0;
  只使用ANSI C/ANSI C++ 标准;
  不要调用依赖于编译环境或操作系统的特殊函数。
  所有依赖的函数必须明确地在源文件中 #include <xxx>
  不能通过工程设置而省略常用头文件。

  提交程序时,注意选择所期望的语言类型和编译器类型。

思路

直接暴搜显然会超时。看数据范围暗示此题可用dp。

dp的难点便是如何表示和计算状态。其实说什么最优子结构这些感觉都很虚。重要的是先确定状态表示和状态计算。状态表示一般是用一个二维或者多维数组来表示某个集合。该集合有独特的属性,如求最大值,那么每个集合的属性便是最大值。而状态计算便是,做到不重复不遗漏的“计算”每一个集合的子集。这里的计算根据实际情况来确定是如何计算。如找最大值,那么对于每一个子集都取最大值,那么最终的集合也就是最大值。然后确定如何对子集如何进行计算便可。

那么对于此题,在n个数中取三个数。显然状态太多,很难确定。这里有个很好的办法来应付此类整除倍数题。因为题目中只要选三个数,且是要求最大的。那么我们通过每个数对于k的余数,来将这些数分成各个集合。%k的余数只有0~k-1。如此分类,便把看似众多无关的状态转化成由余数唯一确定的逐个集合们。最后之取这些集合们的前三大的数来组合,便大大节约了时间。

其次还要知晓到

(a+b+c)%k=0 等价于 (a%k+b%k+c%k)%k=0

即多数之和%k,等价于多数先各自%k再求和再%k

如此下来,有了如何处理这些集合的子集的方向。

设dp[i][j]表示选i个数 余数为j的状态。题即要求dp[3][0]。

剩下的详见代码

Code1

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e3+10;
vector<int> a[M];
int dp[4][N];
int main()
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        int t;
        cin>>t;
        a[t%k].push_back(t);//以余数为状态 ,余数相同的数为同一状态 
    }
    memset(dp,-0x3f,sizeof (dp));//初始化 
    dp[0][0]=0;// 取0个数余数为0的值为0 
    for(int i=0;i<k;i++)//枚举余数 0~k-1 
    {	//降序排列 
         sort(a[i].begin(),a[i].end(),greater<int>());
         for(int u=0;u<3&&u<a[i].size();u++)//每种余数的集合只选前三大的数即可 
         {
             int x=a[i][u];//获得余数为i的数 
             for(int j=3;j>=1;j--)// 枚举选的数 
              for(int m=0;m<k;m++)//枚举余数0~k-1 
               dp[j][m]=max(dp[j-1][(m-x%k+k)%k]+x,dp[j][m]);
               //选x和不选x  若选x则选j-1个数时候余数应该是 m-x%k 但是显然可能为负数
			   //假如m=1,k=3,x=5,x%k=5%3=2。m-x%k=1-2=-1,余数显然不会为负数。之所以为负数要理解成上一个状态余数比k小了1 
			   //即3-1=2  2%3=2。所以选j-1个数的余数应该为2 
         }
    }
    cout<<dp[3][0];
}

Code2

将此题类比于01背包问题,显然每个数只有选或者不选两个状态。

#include<bits/stdc++.h>
const int maxn=1e5+5;
using namespace std;
int n,k;
int dp[5][1005]={0},w[maxn],v[maxn];//dp[i][j]为选i个数,对k的余数为j对应的最大和。题目所求即dp[3][0] 
//类似于背包问题,此处的余数即类似于物品的体积。最终物品体积之和%k也要=0
//(a+b+c)%k==0 d等价于(a%k+b%k+c%k)%k==0 
int main()
{
   
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&v[i]);
        w[i]=v[i]%k;//以余数作为体积 
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=2;j>=0;j--)//枚举选择的数 
        {
            for(int t=k-1;t>=0;t--)//余数的范围是k-1~0 枚举余数相当于背包问题中的枚举体积 
            {
                if((j==0&&t==0)||dp[j][t])//选了0个和模k为0 or 已经选好了j个和模k为t,更新选j+1个时dp值
                    dp[j+1][(t+w[i])%k]=max(dp[j][t]+v[i],dp[j+1][(t+w[i])%k]);//选v[i]和不选v[i]
            }
        }
    }
    cout<<dp[3][0]<<endl;
    return 0;
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prudento

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值