最近没怎么写题解。主要是老师有规定,所以不花费太多的时间。
今晚状态不佳,所以来梳理梳理题目醒醒脑……
Description
被认为天才的小头遇到麻烦了!!这天数学课老师给出了一道难题,而小头居然没能在3秒内解决,可见此题难度之大。
问题是这样的:n个整数围成一个环,老师要求选出其中的若干数,使得选中的数所组成的环中,两个相邻数的差的绝对值不等于1。在满足这个前提下,问最多能取多少个数。
Input
第一行一个正整数n,表示有n个数
第二行n个整数,a1、a2……an 按顺时针方向围成一个环。
Output
一个正整数,即表示最多能选多少个数。
Sample Input
5
1 2 3 5 2
Sample Output
3
Data Constraint
Hint
【样例解释】
最多能选3个数
既选择(1,3,5)或者(2,5,2)
【数据范围】
30%的数据,n≤10
50%的数据,n≤100
70%的数据,n≤1000
100%的数据,n≤100000,ai≤1100000
题目解法
考虑两种解法,后面有彩蛋哦~
方法1
线段树+DP
首先断环成链(复制一段到后面),然后考虑设
f
i
f_{i}
fi为到i的方案数。
因为对于每一个
a
i
a_{i}
ai,因为不让
a
b
s
(
a
j
−
a
i
)
=
=
1
abs(a_{j}-a_{i})==1
abs(aj−ai)==1,
a
j
≠
a
i
−
1
/
a
i
+
1
a_{j}\not=a_{i}-1/a_{i}+1
aj=ai−1/ai+1
所以有三个区间可以转移(
a
i
+
2
a_{i}+2
ai+2,1100000),(
a
i
a_{i}
ai,
a
i
a_{i}
ai),(
0
0
0,
a
i
−
2
a_{i}-2
ai−2)
用线段树维护区间
f
f
f的最大值,当
a
j
a_{j}
aj在这三个区间时,其中
f
j
f_{j}
fj的最大值。
然后转移即可。因为复制了一遍,所以最后输出线段树的第一个节点/2即可。
时间复杂度=枚举i,线段树查询、修改,O(nlog(n))
#include<bits/stdc++.h>
#define N 200005
#define A 1100000
#define inf 1000000007
#define ri register int
using namespace std;
int n,ans,a[N],f[N],tree[A*4];
void find(int k,int l,int r,int L,int R)
{
if(l==L&&r==R) ans=max(ans,tree[k]);
else
{
int mid=(l+r)>>1;
if(R<=mid) find(k<<1,l,mid,L,R);
else
{
if(L>mid) find(k<<1|1,mid+1,r,L,R);
else find(k<<1,l,mid,L,mid),find(k<<1|1,mid+1,r,mid+1,R);
}
}
}
int query(int x,int y)
{
if(x<0||y<0) return 0;
ans=0,find(1,0,A,x,y);return ans;
}
void change(int k,int l,int r,int L)
{
if(l==r) tree[k]=ans;
else
{
int mid=(l+r)>>1;
if(L<=mid) change(k<<1,l,mid,L);
else change(k<<1|1,mid+1,r,L);
tree[k]=max(tree[k<<1],tree[k<<1|1]);
}
}
int main()
{
scanf("%d",&n);
for(ri i=1;i<=n;++i) scanf("%d",&a[i]),a[n+i]=a[i];
for(ri i=1;i<=n*2;++i) ans=f[i]=1+max(query(0,a[i]-2),max(query(a[i],a[i]),query(a[i]+2,A))),change(1,0,A,a[i]);
printf("%d",tree[1]/2);
return 0;
}
方法2
相对于前一种线段树的方法,方法二就显得码量小,实现易,速度快了。
考虑
i
i
i前有3个互不相同且是1——i-1中最大的数。
容易想到,这三个数里面,肯定有一个可以和
a
i
a_{i}
ai转移。
所以我们维护一下这三个数(a1、a2、a3)以及它们对应的(f1、f2、f3)即可。然后再根据性质推断转移一波即可。
时间复杂度O(n)
#include<bits/stdc++.h>
#define N 100005
#define inf 1000000007
#define ri register int
#define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
using namespace std;
int n,a[N*2],f[N*2],f1,f2,f3,a1,a2,a3,ans=-inf;
inline int read()
{
int re=0,ra=1;
char str=getchar();
while(str>'9'||str<'0'){if(str=='-') ra=-1;str=getchar();}
while(str>='0'&&str<='9')re=(re<<1)+(re<<3)+str-'0',str=getchar();
return re*ra;
}
int main()
{
n=read();
for(ri i=1;i<=n;++i) a[i+n]=a[i]=read();
f1=f2=f3=a1=a2=a3=0;
for(ri i=1;i<=n*2;++i)
{
if(abs(a1-a[i])!=1) f[i]=f1+1;
else if(abs(a2-a[i])!=1) f[i]=f2+1;
else f[i]=f3+1;
ans=max(ans,f[i]);
if(f[i]>f1)
{
if(a[i]==a2) a2=a1,f2=f1;
else if(a[i]!=a1) a3=a2,f3=f2,f2=f1,a2=a1;
f1=f[i],a1=a[i];
}
else if(f[i]>f2&&a[i]!=a1)
{
if(a[i]!=a2) f3=f2,a3=a2;
f2=f[i],a2=a[i];
}
else
if(f[i]>f3&&a[i]!=a1&&a[i]!=a2)
f3=f[i],a3=a[i];
}
printf("%d",ans/2);
return 0;
}
加了快读1002byte~~
彩蛋
其实前两种方法都可以被一组数据hack掉
6
3 5 1 6 2 4
答案为4
题解是这么解释的:
30%的分数可以用朴素搜索得到
50%的分数可以用O(N3 )的dp,即枚举起点将环化成一行,然后暴力dp,该算法优化后可以得60~70分
对于100%的数据,需要发现一个性质。即枚举完起点把环转化成一行后,用dp[i]表示以第i个数结尾最多能选多少个数,那么在求dp[i]时只需要记录dp[1]到dp[i-1]里前3大的数就可以了(显然3个不同数中至少有一个和a[i]的绝对值不等于1)。同理,在枚举起点时也只要枚举前5个不同的数。因此该题复杂度为O(N)。
但我现在都还没搞懂为什么要在枚举起点的时候枚举5个不同的数啊QWQ,所以还没实现……