约瑟夫环小结

题目大意:

输入初始人数编号为1~n,以及初始密码m,输出出局序列.

解法一:用线段树可解.

Segtree节点存储左右区间和该区间下包含的人数.

void Build(int p,int left,int right)表示从编号为p,区间为[l,r]的节点开始向下建树.

int Update(int p,int id)表示从编号为p的节点开始查询在新队伍中编号为id的人出局,返回该人在最初队伍中的编号.

而temp=(temp+m-1)%seg[1].manum;语句用于求出下一个出局的人在新队伍中的编号id,即传入Update()函数参数中的id.

Update()函数在递归过程中实现了给新队伍人员从1~n重新编号,如果id<=seg[p].manum,即p节点左子树最多能从1编号到seg[p].manum.

说明新队伍中编号为id的人在p节点的左子树中,则继续递归;反之,则在p节点的右子树中.

#include<cstdio>
#define maxn 200010
struct Segtree
{
    int l,r;
    int manum;
}seg[4*maxn];
void Build(int p,int left,int right)
{
    seg[p].l=left;
    seg[p].r=right;
    if(left==right){
        seg[p].manum=1;
        return;
    }
    int mid=(left+right)/2;
    Build(2*p,left,mid);
    Build(2*p+1,mid+1,right);
    seg[p].manum=seg[2*p].manum+seg[2*p+1].manum;
}
int Update(int p,int id)
{
    seg[p].manum--;
    if(seg[p].l==seg[p].r) 
        return seg[p].l;
    if(id<=seg[2*p].manum)
        Update(2*p,id);
    else
        Update(2*p+1,id-seg[2*p].manum);
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF){
        printf("%d",m);
        Build(1,1,n);
        Update(1,m);
        int temp=m;
        while(n>=2){
            temp=(temp+m-1)%seg[1].manum;
            if(temp==0) temp=seg[1].manum;
            int id=Update(1,temp);
            printf(" %d",id),n--; 
        }
        printf("\n");
    }
}

解法二:链表实现

#include<cstdio>
#include<cstdlib>
using namespace std;
typedef struct Lnode
{
    int id;
    int code;
    struct Lnode *next;
}Lnode,*Linklist;

Linklist Createlist(int n)//创建链表 
{ 
    Lnode *head=NULL,*tail=NULL;
    for(int i=1;i<=n;i++){
          //Lnode *p=(Lnode *)malloc(sizeof(struct Lnode));
          Linklist p=(Linklist)malloc(sizeof(Linklist));
          p->id=i;
          printf("输入第%d个人的密码:",i);
          scanf("%d",&p->code);
          if(tail)
               tail->next=p;
          else
               head=p;
          tail=p;
    }
    if(tail)//成环
          tail->next=head;
    return tail;
}
void print(Linklist tail)//输出 
{
    Linklist head=tail;
    Linklist p=tail->next;
    printf("每个人的初始序号和初始密码:\n"); 
    while(p!=head){//head标志起点
          printf("(%d %d) ",p->id,p->code);
          p=p->next;
    }
    printf("(%d %d)",p->id,p->code);
}
Linklist play(Linklist& tail,int n,int m)//进行游戏 
{
    Linklist pre,p;
    pre=tail;
    printf("出列顺序为:");
    while(n>1){
         p=pre->next;
         m==m%n?m%n:n;//=
         for(int i=1;i<=m-1;i++){
            pre=p;
            p=p->next;
         }
         printf("%d ",p->id);
         //p=p->next
         pre->next=p->next;
         m=p->code;
         n--;
    }
    printf("%d\n\n",pre->id);//pre始终指向为待出列人前驱 
}

int main()
{
    int n,m;
    printf("输入总人数和初始密码:"); 
    while(scanf("%d",&n),n){ 
        scanf("%d",&m);
        printf("*人数为0则游戏结束*\n");
        Linklist tail=Createlist(n);
        if(tail){
             print(tail);
             play(tail,n,m);
        }
        printf("输入总人数和初始密码:");
    }
    return 0; 
} 

解法三:数组模拟可解

#include<bits/stdc++.h>
#define maxn 50
using namespace std; 
bool in[maxn];
int a[maxn];
int c[maxn],t[maxn];
int main()
{
    memset(in,0,sizeof(in));
    int i,j,k,m,n,code,*p;
    printf("请输入总人数和初始密码:");
    scanf("%d%d",&n,&code);
    p=c;
    for(i=0;i<n;i++){
        *(p+i)=i+1;
        printf("第%d个人密码:",i+1);
        scanf("%d",&t[i]);//t[i]中存密码 
    }
    i=0,j=0;
    k=0;//k代表报出号码数 
    m=0;//m代表退出的人数 
    while(m<n){
        if(!in[i])
            k++;
        if(k==code){
            a[j]=(*(p+i));//退出则保存 
            in[i]=1;//设入队标志 
            k=0;
            m++,j++;
            code=t[i];
            code=code%n?code%n:n;//减少循环次数  
        } 
        i++;
        if(i==n)//每一个in[i]都判断 
            i=0;
    }
    printf("出列顺序为:"); 
    for(i=0;i<j;i++)
        printf("%d ",a[i]);
    return 0;
} 

未完待续,持续更新中……

    原文作者:despair_ghost
    原文地址: https://www.cnblogs.com/freinds/articles/6424107.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞