【JZOJ6288】旋转子段

本文介绍了一种通过翻转区间来优化算法的巧妙思路。利用前缀和计算原串不调整的方案数,针对每种翻转,找到翻转中心并排序翻转长度,逐步增加方案数,最终得到最优解。此方法适用于处理区间翻转问题,通过精确计算翻转影响,避免重复计算,提高算法效率。

description


analysis

  • 可以先用前缀和把原串不调整的方案数先求出来

  • 对于一种翻转,肯定是把[i..a[i]][i..a[i]][i..a[i]][a[i]..i][a[i]..i][a[i]..i]这段区间翻转

  • 也可以看做是以i+a[i]2{i+a[i]}\over 22i+a[i]这个点为翻转中心来翻转区间

  • 于是把所有nnn个翻转中心搞出来,用vectorvectorvector存下翻转长度

  • 对于每个翻转中心点,把翻转长度排一下序,从小到大做

  • 由于当前长度翻转只会影响一个点从不合法点变成合法点,所以每次方案递增

  • 左右端点[l,r][l,r][l,r],每次lll变小rrr变大,方案数递增,然后加上该区间以外的方案来更新答案

  • 可以说是比较巧妙的思路了


code

#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#define MAXN 100005
#define INF 1000000007 
#define ll long long
#define reg register ll
#define fo(i,a,b) for (reg i=a;i<=b;++i)
#define fd(i,a,b) for (reg i=a;i>=b;--i)

using namespace std;

ll a[MAXN],sum[MAXN],pos[MAXN];
vector<ll>v[MAXN<<1];
ll n,ans;

inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0' || '9'<ch){if (ch=='-')f=-1;ch=getchar();}
	while ('0'<=ch && ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
inline ll get(ll x,ll y){return sum[y]-sum[x-1];}
int main()
{
	freopen("rotate.in","r",stdin);
	freopen("rotate.out","w",stdout);
	n=read();
	fo(i,1,n)pos[a[i]=read()]=i,sum[i]=sum[i-1]+(a[i]==i);
	fo(i,1,n)v[i+pos[i]].push_back(abs(i-pos[i])+1);
	ans=sum[n];
	fo(i,1,n<<1)
	{
		if (v[i].size()==0)continue;
		ll mid=i/2,cnt=0;
		sort(v[i].begin(),v[i].end());
		fo(j,0,v[i].size()-1)
		{
			ll len=v[i][j],l,r;
			if (i%2==0)l=mid-len/2,r=mid+len/2;
			else l=mid-len/2+1,r=mid+len/2;
			++cnt;
			ans=max(ans,get(1,l-1)+cnt+get(r+1,n));
		}
	}
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值