利用树状数组解决约瑟夫的环

本文介绍了一种使用树状数组高效求解约瑟夫环问题的方法。通过不断加k模n并更新树状数组来快速计算每轮被淘汰者的编号,最终得到完整淘汰顺序。相较于传统的链表法,此方法的时间复杂度显著降低。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此问题非常经典,在网上即可找到原题,在此不做描述。

对于原问题模型,一有链表法解决问题,效率极低,在此描述一种用树状数组完成问题的超快速做法。

首先,我们可以有这样递推的思路:不断加k模n,并减去其数字前走了的人即为当前人的真实编号(即是这一轮应踢走的人的编号),如何快速维护每个人其前走了的人的和,答案为树状数组。


现在模拟一下过程,假设有6个人,k=3(每报3个,走一个人)。

初始状态:1 2 3 4 5 6

用树状数组在每个人的位置加一,可得前缀和:1 2 3 4 5 6

现在1+2(其实是k-1)=3走了:1 2 4 5 6

用树状数组在3的位置减1,可得前缀和:1 2 2 3 4 5

再走了3+2=5,5%(6-1)=5(走了一个人,故6需减1)等等,此时并不是走了5,而是在树状数组中前缀和为5的数字,由上可知走了6:1 2 4 5

用树状数组在6的位置减1,可得前缀和:1 2 2 3 4 4

又按5+2=7,7%(6-2)=3,但这时在树状数组的前缀和中查找3,可见是第四个人的状态为3,故此回合走了4:1 2 5

用树状数组在4的位置减1,可得前缀和:1 2 2 2 3 3

又按3+2=5,5%(6-3)=2,在树状数组中查找二,可见即为2,

故此回合走了2:1 5,

前缀和改为:1 1 1 1 2 2

最后2+2=4,4%(6-4)=2,在树状数组中查找2,可见为5,

故最后只剩1了。

因为前缀和是单调的,所以查找可以用二分。

可得序列为:3 6 4 2 5 1

//不信的话可以和链表验证一下。

参考程序:

#include<cstdio>    
#include<algorithm>    
#include<cstring>    
#include<ctime>    
#define maxn 1000000    
using namespace std;    
int bit[maxn];    
int n,k;    
int sum(int i){    
    int s=0;    
    while (i>0){    
        s+=bit[i];    
        i-=(i&(-i));    
    }    
    return s;    
}    
void add(int i,int x){    
    while (i<=n){    
        bit[i]+=x;    
        i+=(i&(-i));    
    }    
}    
int binary_search(int id){    
    int l=0,r=n+1;    
    while (l<r){    
        int mid=(l+r)>>1;    
        if (sum(mid)<id)l=mid+1;    
            else r=mid;    
    }    
    return l;    
}    
int main(){    
    freopen("input.in","r",stdin);    
    freopen("output.out","w",stdout);    
    while (scanf("%d%d",&n,&k)==2){    
        int id=1;    
        memset(bit,0,sizeof(bit));    
        for (int i=1;i<=n;i++)    
            add(i,1);    
        for (int i=1;i<=n;i++){    
            id=(id+k-2)%(n-i+1)+1;    
            int newid=binary_search(id);    
            printf("%d ",newid);    
            add(newid,-1);    
        }    
    }    
    printf("\nUsed Time=%.2f",(double)clock()/CLOCKS_PER_SEC);    
    return 0;    
}    
为什么说这种方法快呢?

1.检验:在n=100000的情况下,仅费时0.24秒。

2.可以明显得O(n*(logn)^2),应该算是很快了。

对于只需要最后幸存者的问题,可参见递推解决
转载自: http://blog.youkuaiyun.com/no1_terminator/article/details/51820165
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值