JZOJ.1347. 环中环

2 篇文章 0 订阅

最近没怎么写题解。主要是老师有规定,所以不花费太多的时间。
今晚状态不佳,所以来梳理梳理题目醒醒脑……


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(ajai)==1 a j ≠ a i − 1 / a i + 1 a_{j}\not=a_{i}-1/a_{i}+1 aj=ai1/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 ai2
用线段树维护区间 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,所以还没实现……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值