此题本质上是一个01背包问题(提高课状态机模型)(花了好长时间才看懂我是废物)
题目大意:
对于x1,x2..xn来说,从中挑出一些数,使得序列中的数互相之差不等于K.
分析:
可以把序列中的数分为两类
1.对于数X可以找到X+k或X-k.
2.对于数X找不到X+k或X-k.
先看第二类:对于x+k,x-k不存在,所以这种情况的每一个数都符合题目的要求,直接加到答案中,然后删去这些数即可.
对于第一类又可以分为两类:
1.对于序列中某一段x1,x2...xn
,由于其差都为k,所以可以构成一个等差数列.
2.对于序列中某一段可能存在多条等差数列.(解决:将序列中的等差数列预处理出来后保存,这样可以将这段序列转化成P条等差数列)
对于第K条等差数列x1,x2,x3..xn
中的数xi
,其都有选或者不选两种方案.
设状态定义为:f[i][3]
选到第i个数,且第i个数选或者不选所构成非等差序列的最长长度.(即从等差序列中选出最长的非等差序列)
根据题目限制:
1.当不选第i个,则第i-1个选或着不选.
2.选了第i个,则第i-1个一定不能选
状态转移方程:
f[i][1]=max(f[i][1],f[i-1][0]+num[b[k][i]]);//b[k][i]表示第k段等差数列中的第i个元素
f[i][0]=max(f[i][0],max(f[i-1][0],f[i-1][1]));
注意:本题需要去重,不去重的话还得把所有相同数的答案加起来导致不好处理.
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
//本质:多个01背包
int n,k;
int num[N];//每个元素出现的次数
int temp[N];//初始数组
vector<int> a;//排除无关元素后的数组
vector<int> b[N];//存储k段等差数列
int cnt;
int res;//最终答案
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>temp[i],num[temp[i]]++;
for(int i=1;i<=n;i++)
if(num[temp[i]+k]||(temp[i]>k&&num[temp[i]-k])) a.push_back(temp[i]);
else res++;//第二类
sort(a.begin(),a.end());
a.erase(unique(a.begin(),a.end()),a.end());//去除无关元素,留下来一定是可以配成<x-k,x>的元素
//预处理出所有的等差数列
int st[N],len=a.size();
memset(st,0,sizeof st);
for(int i=0;i<len;i++)
{
if(!st[a[i]])
{
int t=a[i];
while(num[t]>0&&!st[t]) b[cnt].push_back(t),st[t]=1,t+=k;
cnt++;
}
}
//对每段等差数列做一遍01背包
for(int i=0;i<cnt;i++)
{
int f[N][3];//f[i][0]不选第i个元素,f[i][1]选第i个元素
int size1=b[i].size();
memset(f,0,sizeof f);
for(int j=0;j<size1;j++)
{
f[j][1]=max(f[j][1],f[j-1][0]+num[b[i][j]]);
f[j][0]=max(f[j][0],max(f[j-1][0],f[j-1][1]));
}
res+=max(f[size1-1][0],f[size1-1][1]);
}
cout<<res;
return 0;
}