这是题目链接题目传送门,感兴趣的小伙伴可以点击进去提交。
题目大致意思就是给你两个序列a和b,元素的个数均为n,且b中元素属于[0,100],a中元素属于[0,100000],可以将a[i]进行两种操作,a[i]-b[i],或者a[i]+b[i],或者不变,然后的到一个新的序列a^,最终目的是使得,新的序列和%y的值最大,y属于[0,100]。
分析:
这题一看y的数据范围,和题目的限制条件第一时间直接想到dp,第一种很容易想到的就是二维dp[i][j],代表前i个元素可以组成为j的情况是否成立。这样我们就需要反向枚举j的成立的值即可得到最大结果。核心代码:状态转移方程。
if(dp[i-1][j]{//这里代表前一个合法j的状态
//下面就是枚举三种状态=1代表覆盖的j成立的值
dp[i][(j+a[i])%y] = 1;
dp[i][(j+a[i]+b[i])%y] = 1;
dp[i][(j+a[i]-b[i])%y] = 1;
}
实现代码:
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int b[100005];
int dp[100010][110];
int main()
{
int n,y;
cin>>n>>y;
long long sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
dp[1][a[1]%y]=1;
dp[1][(a[1]+b[1])%y]=1;
dp[1][(a[1]-b[1])%y]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=100;j++)
{
if(dp[i-1][j])
{
dp[i][(j+a[i])%y] = 1;
dp[i][(j+a[i]+b[i])%y] = 1;
dp[i][(j+a[i]-b[i])%y] = 1;
}
}
}
for(int i=100;i>=0;i--)
{
if(dp[n][i])
{
cout << i << endl;
return 0;
}
}
}
第二种就是讲dp的空间压缩到一维,这里我们考虑到当前状态一定是由上一步的状态转移过来的,所以我们只需要记录下上一步的所有状态就可以直接根据%y的取值(也就是y的那个维度)进行状态的转移,状态记录只需要开一个vis数组大小110就可以,因为y的范围只是[0,100]。然后每次利用vis数组对当前转移数组状态进行更新。其实就是暴力我感觉哈哈、细节见代码注释。
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int b[100005];
int dp[110];
//这题考虑直接以为一个维度dp解决,那么需要在当前状态进行下一个状态的转移就需要记录下当前的状态。
int vis[110];//记录下一个状态的标记数组
int main()
{
int n,y;
cin>>n>>y;
long long sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
dp[sum%y]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<y;j++)
{
vis[j]=0;
}
for(int j=0;j<y;j++)
{
if(dp[j])
{
vis[(j+b[i])%y] = 1;
vis[(j-b[i]+y)%y] = 1;
//这里注意取mod的时候考虑负数
vis[j]=1;
}
}
//这里是核心,用当前状态覆盖前一步状态。
for(int j=0;j<y;j++)
{
dp[j]=vis[j];
}
}
for(int i=y-1;i>=0;i--)
{
if(dp[i])
{
cout << i << endl;
return 0;
}
}
}