本篇是寒假的第二弹,虽然每天都有练习算法,但是假期的话想要挤出时间写博客还是比较紧张,还有Linux要学;今天就写了一道算法题,剩下的时间拿出来写博客吧!话不多说上成果!!!
(1)钻石收集者
以以前写的代码展开详细描述与解释,并附上题目

cpp
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e4+10;
int n,k;
int a[N];
int f[N],g[N];
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
for(int left=1,right=1;right<=n;right++)
{
while(a[right]-a[left]>k) left++;
f[right]=max(f[right-1],right-left+1);
}
for(int left=n,right=n;left>=1;left--)
{
while(a[right]-a[left]>k) right--;
g[left]=max(g[left+1],right-left+1);
}
int ret=0;
for(int i=2;i<=n;i++)
{
ret=max(ret,f[i-1]+g[i]);
}
cout<<ret<<endl;
return 0;
}
这是一道在洛谷上写的题目,所以使用全局的变量更加方便,根据输入要求,我直接把数组设置的足够大,那么就不用考虑越界的问题
实现本题的逻辑主要在于滑动窗口思想
- 首先,需要将数组从大到小依次排序起来这样,方便后面遍历数组
- 数组f[N]:从左往右统计一次,正向
- 数组g[N]:从右往左统计一次,反向(这两个数组是实现滑动窗口思想的底层容器)
- f[right]:表示以right为结尾的数段中,所能达到的最大长度
- g[left]:表示以left为开头的数段中,所能达到的最大长度
- f[i-1]+g[i]:表示以i位置分割的左右两部分,左右结合起来能达到的最大长度
- ret:因为最大长度可能存在任意一个位置,所以需要暴搜,找寻最终的答案
(2)是7倍数的最长子序列
以以前写的代码展开详细描述与解释,并附上题目

cpp
#include<iostream>
#include<cstring>
using namespace std;
int n;
int id[10];
int main()
{
cin>>n;
memset(id,-1,sizeof id);
id[0]=0;
int sum=0,ret=0;
for(int i=1;i<=n;i++)
{
int x;cin>>x;
sum=(sum+x)%7;
if(id[sum]!=-1) ret=max(ret,i-id[sum]);
else id[sum]=i;
}
cout<<ret<<endl;
return 0;
}
这是一道在洛谷上写的题目,所以使用全局的变量更加方便,根据输入要求,我直接把数组设置的足够大,那么就不用考虑越界的问题
实现本题的逻辑主要在于前缀和+哈希表+同余定理
- 数组id:本道题使用数组代替哈希表,这样可以节省空间,因为任意一个数%7后,数值都在0~6之间,所以我们数组不用开太大,就开10即可
- 初始化:一开始把数组都初始化为-1,这样就可以清楚这个位置我们是否已经遍历过与否,此处值仍为-1,就是还没有遍历过;不为-1,就是已经存进去了一个结点下标
我们需要特地将id[0]这个点初始化为0,因为如若整个序列就是7的倍数的话,那么计算 的时候,就是拿此时id[0]内的下标进行计算
- 实现逻辑:因为是要找到最长的子序列,那么我们只要存%7的0~6这几个数第一次出现的下标即可;后续若重复出现,就可以计算此时的长度,与ret比较
- 同余定理:文字不方便描述,可以到绑定的资源内看图解,更好理解
(3)Zuma
以今天写的代码展开详细描述与解释,并附上题目

cpp
#include<iostream>
#include<cstring>
using namespace std;
const int N=521;
int n;
int a[N];
int f[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++)
f[i][i]=1;
for(int i=1;i+1<=n;i++)
{
int j=i+1;
if(a[i]==a[j]) f[i][j]=1;
else f[i][j]=2;
}
for(int len=3;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
for(int k=i;k<j;k++)
{
//[i,k][k+1][j]
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
}
if(a[i]==a[j]) f[i][j]=min(f[i][j],f[i+1][j-1]);
}
}
cout<<f[1][n]<<endl;
return 0;
}
这是一道在洛谷上写的题目,所以使用全局的变量更加方便,根据输入要求,我直接把数组设置的足够大,那么就不用考虑越界的问题
实现本题的逻辑主要在于区间dp
- 状态表示f[i][j]:把区间[i,j]内的宝石全部移走,所需要的最少步骤
- 状态表示:
①基于分割点k考虑:f[i][k]+f[k+1][j]
②基于左右端点考虑:当a[i]==a[j]时,f[i+1][j-1](容易遗漏,则会错过最优解)
- 本题难点初始化:
①先将所有的位置初始化为无穷大
②再把所有的基础长度为1,2的区间初始化
③注意f[i][j]初始化时,因为会用到f[i+1][j-1]初始化,但是如若不进行第二部,那么两个位置都是无穷大,就无法取出最小值,进行后续的dp(详情可以去绑定的资源内看图解)
