[ARC119E]Pancakes

Pancakes

题解

很水的一道题

首先,它最多只能有一次翻转,而它的权值又是相邻的两项去绝对值,所以最多只会影响到两项的权值。
假设我们翻转的是 l , r l,r l,r,那么我们要求的就是 ( ∣ a l − a l − 1 ∣ + ∣ a r − a r + 1 ∣ − ∣ a r − a l − 1 ∣ − ∣ a l − a r + 1 ∣ ) max ⁡ \left(\left|a_{l}-a_{l-1}\right|+\left|a_{r}-a_{r+1}\right|-\left|a_{r}-a_{l-1}\right|-\left|a_{l}-a_{r+1}\right|\right)_{\max} (alal1+arar+1aral1alar+1)max

我们可以将所有的 a a a看作一个点,那么每个绝对值就是一条线段,从 a l a_{l} al a l + 1 a_{l+1} al+1的有向线段。
我们要做的,就是交换两个线段的起点,使他们减小的覆盖面积最大。
于是,我们很容易就联想到了[CF1513F]Swapping Problem这道题。
只不过这道题要使的两条线段异向,而这道题只有同向才会产生贡献。
但这并不影响我们用相似的方法完成这道题。

我们可以先将所有得同向线段按照左端点排序,再记录下右端点前缀最大值。
对于线段 i i i,线段 i − 1 i-1 i1就是与它左端点相交的最近的线段,我们需要将它的前缀最大值更新进去。
而在右边,我们需要去二分出第一个右端点 > > >线段 i i i的右端点的线段,用它来更新有它右端点相交的长度。注意必须是严格大于,否则可能会被线段 i i i的右端点影响。
而在 i i i与右边二分的那一条线段之间,就是被它包含的线段,我们可以用 s t st st表求出被它包含的最长线段长度。
由于第 n n n个点比较特殊,它没有向后连的线段,我们还要特别更新一下它们与其它点交换会带来的贡献。

时间复杂度 O ( n l o g   n ) O\left(nlog\,n\right) O(nlogn)
update 2021.5.18: 经PPL巨佬提醒发现好像直接用排完序后贪心就可以做,如果用基排排序的话可以做到 O ( n ( l o g r m + r ) ) O(n(log_{r}m+r)) O(n(logrm+r))不过应该没人用基排吧

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 300005
#define lowbit(x) (x&-x)
#define reg register
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x7f7f7f7f;
const LL jzm=2333;
const double Pi=acos(-1.0);
typedef pair<int,int> pii;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int n,a[MAXN],tot,tot1,tot2,maxx,st[25][MAXN],mlim[MAXN],lg[MAXN];LL ans;
struct ming{int a,b,id;}up[MAXN],dn[MAXN];
bool cmp(ming x,ming y){if(x.a==y.a)return x.b>y.b;return x.a<y.a;}
int query(int l,int r){if(l>r)return 0;int k=lg[r-l+1];return max(st[k][l],st[k][r-(1<<k)+1]);}
signed main(){
	read(n);for(int i=1;i<=n;i++)read(a[i]);LL maxx=0;
	for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
	for(int i=1;i<n;i++)ans+=1ll*Fabs(a[i]-a[i+1]);
	for(int i=1;i<n;i++)
		if(a[i]<a[i+1])up[++tot1]=(ming){a[i],a[i+1]};
		else dn[++tot2]=(ming){a[i+1],a[i]};
	sort(up+1,up+tot1+1,cmp);sort(dn+1,dn+tot2+1,cmp);
	for(int i=1;i<=tot1;i++)st[0][i]=up[i].b-up[i].a,mlim[i]=max(mlim[i-1],up[i].b);
	for(int i=1;i<=lg[tot1];i++)
		for(int j=1;j<=tot1-(1<<i)+1;j++)
			st[i][j]=max(st[i-1][j],st[i-1][j+(1<<i-1)]);
	for(int i=1;i<=tot1;i++){
		maxx=max(maxx,2ll*(min(up[i].b,mlim[i-1])-up[i].a));int l=i+1,r=tot1;
		while(l<r){int mid=l+r>>1;if(mlim[mid]>up[i].b)r=mid;else l=mid+1;}
		maxx=max(maxx,2ll*query(i+1,min(tot1,l-1)));
		if(l<=tot1&&up[l].b>up[i].b)maxx=max(maxx,2ll*(up[i].b-up[l].a));
	}
	for(int i=1;i<=tot2;i++)st[0][i]=dn[i].b-dn[i].a,mlim[i]=max(mlim[i-1],dn[i].b);
	for(int i=1;i<=lg[tot2];i++)
		for(int j=1;j<=tot2-(1<<i)+1;j++)
			st[i][j]=max(st[i-1][j],st[i-1][j+(1<<i-1)]);
	for(int i=1;i<=tot2;i++){
		maxx=max(maxx,2ll*(min(dn[i].b,mlim[i-1])-dn[i].a));int l=i+1,r=tot2;
		while(l<r){int mid=l+r>>1;if(mlim[mid]>dn[i].b)r=mid;else l=mid+1;}
		maxx=max(maxx,2ll*query(i+1,min(tot2,l-1)));
		if(l<=tot2&&dn[l].b>dn[i].b)maxx=max(maxx,2ll*(dn[i].b-dn[l].a));
	}
	for(int i=2;i<n;i++)
		maxx=max(maxx,1ll*Fabs(a[i]-a[i-1])-1ll*Fabs(a[n]-a[i-1])),
		maxx=max(maxx,1ll*Fabs(a[i+1]-a[i])-1ll*Fabs(a[1]-a[i+1]));
	printf("%lld\n",ans-maxx);
	return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值