题解 P1667 【数列】
2021.11.7 update:
补充了被hack的问题。(格式可能略有些不符合新版规定,看在本来是一楼题解的份上放我一马吧呜呜)
对于一个区间
那么我们在前缀和(设为
而且我们注意到,本来就有
给一个前缀和序列,可以在其中任意交换2个数,最后让这个序列变为单调递增的。
注意:在前缀和序列里不能有负数和相等的,若有就输出-1(自己证吧,我懒癌犯了。。)
对于这个新问题,我们先离散化,然后找“循环节”,就可以了。
不知道循环节的小伙伴看这里(其他人可以走了):
对于一个数列,我们想把它变成单调递增的该最少交换几次呢?
我们知道,若是交换必须是相邻的就是一道逆序对水题了(P1774),而现在任意交换,那么我们这么考虑:
采用贪心的思想:每个数都有一个自己的位置,那么我们就每当遇到一个位置上面的数不正确,就把应该在这个位置的数和当前这个位置的数互换。这样可以想到,最多换
而事实上,我们在换的时候若是每次都可以满足两个位置(太好了!),那么就是
所以我们要尽可能的找更多的循环节,那怎么办呢?(其实上面那个第一次的贪心的思想就是正解。。)
我们发现,若是有3个数成循环节,那么我就算把其中的数的位置换了,也不会破坏这个循环节(交换是在数列里任意两个数),所以,其实我不用刻意去找循环节(它会自己来找你。。。),我只要每次都保证我的每一步交换都至少有一个位置上的数正确了,最后剩下的数的循环节一定还能使用,从而减小交换次数。
所以,说了这么半天原来
这么水??!!
希望大家听懂了。
tip:其实那个贪心的交换不好实现,(逃
code:
#include<bits/stdc++.h>
using namespace std;
int a[100010],p[100010],s[100010],stmp[100010];
inline bool cmp(int x,int y){
return s[x]<s[y];
}
inline void halt(){
puts("-1"); exit(0);
}
int main(){
int n,mx=0;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
p[i]=i;s[i]=s[i-1]+a[i],stmp[i]=s[i];
mx=stmp[n];
}
sort(stmp+1,stmp+1+n);
if(stmp[1]<=0 || stmp[n]!=mx) halt();
for(int i=1;i<n;i++) if(stmp[i]==stmp[i+1]) halt();
sort(p+1,p+1+n,cmp);//该排在第i的数的位置为p[i]
for(int i=1;i<=n;i++) s[p[i]]=i;//成功离散化
int ans=n;//不能是n-1哦
for(int i=1;i<=n;i++){
if(s[i]==i) ans--;
else{
swap(p[s[i]],p[i]),swap(s[p[s[i]]],s[i]);
}//这里的两个swap自己找一组数去模拟着理解,其实实现的就是上面那个贪心
}
cout<<ans<<endl;
return 0;
}