2020牛客暑期多校训练营(第六场)J Josephus Transform —— 权值线段树求约瑟夫环置换,O(n)求x次置换之后的位置

This way

题意:

现在有n个人,然后有m次操作,每次操作都做长度为k的约瑟夫,然后再按照淘汰的顺序再做约瑟夫,直到x次。问你最终的序列是怎么样的。

题解:

这种题目我就不是很懂。首先用权值线段树 O ( n l o g n ) O(nlogn) O(nlogn)求出置换。只要每次查看当前位置后面的值够不够,如果不够的话,减掉后面值的个数,然后回到1,然后求从p开始有now个还存在的数的位置。
置换求出来了之后,好像可以用倍增做,但是也有可以快速求经过了x步的做法,然后置换是有两种表示形式的:
就拿第一个样例来举例子
第一种置换是 3 1 5 2 4,a[i]表示上一个在a[i]位置的数会变到i位置
第二种置换:2 4 1 5 3,a[i]表示当前在i位置上的数在下一步会变到a[i]位置
然后对于第二种置换,假设要变换x步,那么第i个位置上的数会变到a[(i+x%len)%len]上。len表示循环的长度,因为位置只在循环内变动。
然后再将答案按照这个置换走一下即可。

#include
using namespace std;
const int N=1e5+5;
int num[N*4];
void build(int l,int r,int root){
    if(l==r){
        num[root]=1;
        return ;
    }
    int mid=l+r>>1;
    build(l,mid,root<<1);
    build(mid+1,r,root<<1|1);
    num[root]=num[root<<1]+num[root<<1|1];
}
void update(int l,int r,int root,int p){
    if(l==r){
        num[root]--;
        return ;
    }
    int mid=l+r>>1;
    if(mid>=p)
        update(l,mid,root<<1,p);
    else
        update(mid+1,r,root<<1|1,p);
    num[root]=num[root<<1]+num[root<<1|1];
}
int q_sum(int l,int r,int root,int ql,int qr){
    if(l>=ql&&r<=qr)
        return num[root];
    int mid=l+r>>1;
    int ans=0;
    if(mid>=ql)
        ans=q_sum(l,mid,root<<1,ql,qr);
    if(mid<qr)
        ans+=q_sum(mid+1,r,root<<1|1,ql,qr);
    return ans;
}
int q_pos(int l,int r,int root,int v){
    if(l==r)return l;
    int mid=l+r>>1;
    if(num[root<<1]>=v)
        return q_pos(l,mid,root<<1,v);
    else
        return q_pos(mid+1,r,root<<1|1,v-num[root<<1]);
}
int a[N],ans[N];
int n,m;
int k,x;
void finds(){//找约瑟夫环
    int cnt=0;
    for(int i=1;i<=n;i++){
        int now=k;
        if(now+cnt>num[1])
            now+=cnt-num[1];
        else
            now+=cnt;
        now%=num[1];
        if(!now)now=num[1];
        int p=q_pos(1,n,1,now);
        a[i]=p;
        update(1,n,1,p);
        if(p-1)
            cnt=q_sum(1,n,1,1,p-1);
        else
            cnt=0;
    }
}
int tmp[N],vis[N],ttmp[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)ans[i]=i;
    while(m--){
        build(1,n,1);
        memset(vis,0,sizeof(vis));
        scanf("%d%d",&k,&x);
        finds();
        for(int i=1;i<=n;i++)//求置换
            tmp[a[i]]=i;
        for(int i=1;i<=n;i++)
            a[i]=tmp[i];
        for(int i=1;i<=n;i++){
            if(vis[i]==1)continue;
            int top=0,now=i;
            vis[now]=1;
            tmp[top++]=now;
            int ne=a[now];
            while(!vis[ne]){
                tmp[top++]=ne;
                now=ne,vis[now]=1,ne=a[now];
            }
            int res=x%top;
            for(int j=0;j<top;j++)//快速置换
                ttmp[tmp[j]]=tmp[(j+res)%top];
        }
        for(int i=1;i<=n;i++)a[i]=ttmp[i];
        for(int i=1;i<=n;i++)
            tmp[a[i]]=ans[i];
        for(int i=1;i<=n;i++)
            ans[i]=tmp[i];
    }
    for(int i=1;i<=n;i++)
        printf("%d%c",ans[i]," \n"[i==n]);
    return 0;
}

你可能感兴趣的:(想法,线段树)