题目
n(n<=5e6)个数,第i个数是ai(1<=ai<=n)
你可以翻转一次[l,r]内的数,使得al al+1 ... ar翻转之后为ar ... al,也可以不翻转
如果ai恰等于它所在的位置i的话,会对答案造成1的贡献
最大化答案,并输出最大值
思路来源
归神
题解
考虑i位置的值是ai,其只有在翻转区间对称轴是(i+ai)/2的时候才能被归位
枚举必翻的位置i,如果i所在这个值归位当且仅当翻转区间[min(i,ai),max(i,ai)]
翻转这么一次,可以使得这个区间内对称轴相同的值都被归位
所以需要统计[min(i,ai),max(i,ai)]区间内,满足下标为j且j+aj==i+ai的下标有多少个
经典问题:统计[l,r]内值为x的数有多少个,
①离散化i+ai的值,建主席树,每次用root[r]和root[l-1]作差,n==5e6,MLE
②把j+aj的vector里放入下标j,询问时在i+ai的vector里二分<=r的数量和<=l-1的数量作差,即[l,r]的数量
离散化一下i+ai的值,可以把vector开成n的规模
[l,r]内的值统计出来了,再统计一下[1,l-1]和[r+1,n]已经归位的数,前缀和和后缀和搞一下
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=5e6+10;
int a[maxn],b[maxn],c[maxn];
int pre[maxn],suf[maxn];
int n,ans,tmp,num,l,r;
vector<int>res[maxn];
int main()
{
scanf("%d",&n)==1;
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i])==1;
c[i]=b[i]=a[i]+i;
pre[i]=pre[i-1];
if(a[i]==i)pre[i]++;
}
for(int i=n;i>=1;--i)
{
suf[i]=suf[i+1];
if(a[i]==i)suf[i]++;
}
sort(b+1,b+n+1);
num=unique(b+1,b+n+1)-(b+1);
for(int i=1;i<=n;++i)
{
c[i]=lower_bound(b+1,b+num+1,c[i])-b;
res[c[i]].push_back(i);
}
ans=pre[n];
for(int i=1;i<=n;++i)
{
l=min(i,a[i]),r=max(i,a[i]);
tmp=upper_bound(res[c[i]].begin(),res[c[i]].end(),r)-upper_bound(res[c[i]].begin(),res[c[i]].end(),l-1);
ans=max(ans,pre[l-1]+tmp+suf[r+1]);
}
printf("%d\n",ans);
return 0;
}
后续
还是不离散化的快一点,就开了2倍的vector还不一定用,毕竟少了排序
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=5e6+10;
int a[maxn];
int pre[maxn],suf[maxn];
int n,ans,tmp,num,l,r;
vector<int>res[2*maxn];
int main()
{
scanf("%d",&n)==1;
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i])==1;
pre[i]=pre[i-1]+(a[i]==i);
res[a[i]+i].push_back(i);
}
for(int i=n;i>=1;--i)
suf[i]=suf[i+1]+(a[i]==i);
ans=pre[n];
for(int i=1;i<=n;++i)
{
l=min(i,a[i]),r=max(i,a[i]);
tmp=upper_bound(res[a[i]+i].begin(),res[a[i]+i].end(),r)-upper_bound(res[a[i]+i].begin(),res[a[i]+i].end(),l-1);
ans=max(ans,pre[l-1]+tmp+suf[r+1]);
}
printf("%d\n",ans);
return 0;
}