[CEOI2018]Global warming

update:2021 06 14 更改了半全角,图片,LaTeX。

Link


ETHANK 大佬已经写过题解,但是对于没有想过来的 friends 可能会有点迷糊,故以此帖加以补充。

通过读题可知我们需要修改的是一个连续区间 [ l , r ] [l,r] [l,r],由于增加值相等,所以区间内的元素相对大小不改变,也就是说,这次修改只会对 [ r + 1 , n ] [r+1,n] [r+1,n] 造成影响。但修改的范围和增加值我们都不知道,这也是本题难点。

我们不妨换个角度思考,修改操作的作用其实就是把某一段高度高的区间降下来,与前后剩余区间的某些元素构成 LIS,或是提升某一段区间的高度,与上面的情况同理。

设增加值为 x,前面一段区间 A 的 LIS 的终点为 a,后面一段区间 B 的 LIS 起点为 b。

  1. a ⁡ < b ⁡ \operatorname{a}<\operatorname{b} a<b,二者直接合并。

  2. a ⁡ < b ⁡ + x ⁡ \operatorname{a}<\operatorname{b}+\operatorname{x} a<b+x,此时相当于提升 B ,两者合并。

如图:(黑色代表 A,橙色代表 B,蓝色代表 [ r + 1 , n r+1,n r+1,n])。

不难发现,a<b+x 与 a-x<b 等价。

如图:

又因为区间内部相对大小不变,所以 A 的减小值肯定越大越好,更有可能与后面构成新的 LIS,而 B 的增加值就不一定了,因为有可能增加了反而超过了后面可充当新 LIS 后半段的区间的元素大小,即能减少到 d,但不一定能增加到 d。

如图:

既然前面区间和我这个区间一起改变后我们之间不会有影响,但可能对后面的区间产生影响,那么肯定每次变动整个前缀最优。

答案即为每次修改后 [ 1 , n ] [1,n] [1,n] 的 LIS 的最大值。

暴力的时间复杂度为 O ( n 2 log ⁡ n ) O(n^2 \log n) O(n2logn)

对于优化,我们可以用动态 LIS ,时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn)

我们也可以用 dp[i] 存储以 i 为终点的 LIS,pd[i] 存储 a[i] 减小后,作为起点在原序列中的 LIS ,则:

ans[i] ⁡ = dp[i] ⁡ + pd[i] ⁡ − 1 \operatorname{ans[i]}=\operatorname{dp[i]}+\operatorname{pd[i]}-1 ans[i]=dp[i]+pd[i]1

dp[i] 和 pd[i] 都可以用 BIT 在 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间内求到,具体操作可以看代码。

不要忘记离散化。

Code:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=2e5+5;
#define int long long
int n,len;
long long a[MAXN<<1],x,BIT[MAXN<<1],b[MAXN<<1],dp[MAXN],pd[MAXN],ans,val[MAXN],Count[MAXN];//Count[i]的update即把a[i]加进原序列
int lowbit(int x)
{
	return x&-x;
}
void update(int x,long long k)//正向LIS
{
	for(int i=x;i<=len;i+=lowbit(i))
	{
		BIT[i]=max(BIT[i],k);
	}
}
void update_op(int x,long long k)//反向LIS
{
	for(int i=x;i;i-=lowbit(i))
	{
		BIT[i]=max(BIT[i],k);
	}
}
long long sum(int x)
{
	long long ans=0;
	for(int i=x;i;i-=lowbit(i))
	{
		ans=max(ans,BIT[i]);
	}
	return ans;
}
long long sum_op(int x)
{
	long long ans=0;
	for(int i=x;i<=len;i+=lowbit(i))
	{
		ans=max(ans,BIT[i]);
	}
	return ans;
}
signed main()
{
	scanf("%lld%lld",&n,&x);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		a[i]+=x;
		b[i]=a[i];
		b[i+n]=a[i]-x;
	}
	sort(b+1,b+1+2*n);
	len=unique(b+1,b+1+2*n)-b-1;
	for(int i=1;i<=n;i++)
	{
		val[i]=lower_bound(b+1,b+1+len,a[i]-x)-b;
		a[i]=lower_bound(b+1,b+1+len,a[i])-b;
	}//离散化
	for(int i=1;i<=n;i++)
	{
		dp[i]=sum(a[i]-1)+1;
		update(a[i],dp[i]);
	}
	memset(BIT,0,sizeof(BIT));
	for(int i=n;i>=1;i--)
	{	
		pd[i]=sum_op(val[i]+1)+1;//因为修改的是前缀,后面的无影响
		Count[i]=sum_op(a[i]+1)+1;//本身要为原来数组的后缀做贡献
		update_op(a[i],Count[i]);
		ans=max(ans,pd[i]+dp[i]-1);
	}
	printf("%lld\n",ans);
	return 0;
}

E n d End End

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值