题意:某人手上有一串珠子,顺时针编号1--n,每次可进行三种操作:顺时针旋转一个珠子,逆时针旋转一个珠子,将手中的珠子移去(移去后顺时针方向下一个珠子进入手中),问按标号12345顺序将所有珠子逐一移去,最少需要多少次操作。
分析:假设当前要移除的珠子为x,x左边的珠子用A代表,右边的珠子用B代表即AxB,则无论左移还是右移都会得到xBA,即移去某个珠子对后面的珠子无影响,每次只需单独考虑A和B的长度,取最小累加即可。
做法:比赛时想复杂了,想维护每个珠子position的变化,一度想用伸展树旋转,没有充分利用循环性。自己找张小纸条转一下就很容易发现规律了(吾,有时过于感性了)
因为序列是循环的,因此每次旋转改变绝对位置不改变相对位置,因此记录当前手中珠子的初始位置cnt,若下个珠子的原始位置为pos,若cnt=pos则不需要转,若cnt<pos,设x为cnt与pos之间的珠子数,n为当前剩余的珠子,则旋转需要min(x,n-x)次cnt>pos时情况类似。用树状数组统计,每次一出后对应位置-1即可。
注意:下一次的位置不需要从当前位置开始顺时针找第一个存在的珠子,只需停在当前位置就好,因为当前位是0,不影响下一次树状数组的统计。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include<cmath>
using namespace std;
int ss[100010],n;
inline int lowbit(int k){
return (k&-k);
}
void inc(int i,int k){
while(i<=n){
ss[i]+=k;
i+=lowbit(i);
}
}
int get(int i){
int sum=0;
while(i>0){
sum+=ss[i];
i-=lowbit(i);
}
return sum;
}
int pos[100010],p;
int main(){
while(scanf("%d",&n)&&n!=0){
for(int i=1;i<=n;i++){
scanf("%d",&p);
pos[p]=i;
ss[i]=lowbit(i);
}
int cnt=1,x;
long long ans=0;
for(int i=1;i<=n;i++){
ans++;
if(cnt!=pos[i]){
x=abs(get(cnt-1)-get(pos[i]-1));
ans+=min(x,n-i+1-x);
}
cnt=pos[i];
inc(pos[i],-1);
}
printf("%I64d\n",ans);
}
}
跑的很快,看来是正解!