首页 > 代码库 > CODEVS1282 约瑟夫问题(线段树)

CODEVS1282 约瑟夫问题(线段树)

题目描述 Description

有编号从1到N的N个小朋友在玩一种出圈的游戏。开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。编号为1的小朋友站在编号为N的小朋友左边。首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。直到只剩下1个小朋友,则游戏完毕。

现在给定N,M,求N个小朋友的出圈顺序。

知识: 最近在做线段树,很是美丽。时间复杂度logn,空间大概在2*n到4*n左右(为避免溢出错误,就开到4*n吧,毕竟伤不起。。。)
(1)单点查询

int work(int i,int l,int r,int k)

{

int mid;

if (l==r&&l==k)

return tree[i];

mid=(l+r)/2;

pushdown(i);

if (k<=mid) return work(i*2,l,mid,k);

else return work(i*2+1,mid+1,r,k); 

}

(2)区间查询

int work(int i,int l,int r,int ll,int rr)

{

int mid,sum=0;

if (ll<=l&&r<=rr)

return tree[i];

mid=(l+r)/2;

if (ll<=mid) sum+=work(i*2,l,mid,ll,rr);

if (rr>mid) sum+=work(i*2+1,mid+1,r,ll,rr);

return sum;

}(求和)

(3)单点修改

void insert(int i,int l,int r,int a,int b)

{

int mid;

if (l==r&&l==a)

{

tree[i]+=b;

return;

}

mid=(l+r)/2;

if (a<=mid) insert(i*2,l,mid,a,b);

else insert(i*2+1,mid+1,r,a,b);

tree[i]=tree[i*2]+tree[i*2+1];

}

(4)区间修改(最复杂,用lazy标记)
 void paint(int i,long long x,int l,int r)

{

tree[i]+=x*(r-l+1);(这里是求和,所以要乘上元素的个数,如果是求最值得话,只需要取min或max就可以了。)

delta[i]+=x;

}

void pushdown(int i,int l,int r)

{

int mid;

mid=(l+r)/2;

paint(i*2,delta[i],l,mid);

paint(i*2+1,delta[i],mid+1,r);

delta[i]=0;

}

void insert(int i,int l,int r,int aa,int b,long long x)

{

int mid;

if (aa<=l&&r<=b)

{

paint(i,x,l,r);

return;

}

pushdown(i,l,r);

mid=(l+r)/2;

if (aa<=mid) insert(i*2,l,mid,aa,b,x);

if (b>mid) insert(i*2+1,mid+1,r,aa,b,x);

tree[i]=tree[i*2]+tree[i*2+1];

}(查询时求和,注意lazy下放)

思路:这些基本的类型有很多变换,如区间替换,空位查询等。本题就是用到了单点修改、区间查询。这里的单点查询并不知道具体位置,而知道在几个人之后,所以有一些复杂,不过对线段树熟练程度越高,也就越简单,万变不离其宗。。。
注意:这里的m要在每次有孩子出去的时候用另一个变量保存,并不断的与tree[1](总人数)比较,因为有可能m比总人数大,这时候就要进行减了。同时必须是另开一个变量,不能再m上减(因为这个,wa了一次)。

code:

#include<iostream>

#include<cstdio>

using namespace std;

int tree[200000]={0},p;

void build(int i,int l,int r)

{

int mid;

if (l==r)

{

tree[i]=1;

   return;

}

mid=(l+r)/2;

build(i*2,l,mid);

build(i*2+1,mid+1,r);

tree[i]=tree[i*2]+tree[i*2+1];

}

int work(int i,int l,int r,int ll,int rr)

{

int mid,sum=0;

if (ll<=l&&r<=rr)

return tree[i];

mid=(l+r)/2;

if (ll<=mid) sum+=work(i*2,l,mid,ll,rr);

if (rr>mid) sum+=work(i*2+1,mid+1,r,ll,rr);

return sum;

}

void insert(int i,int l,int r,int t)

{

int mid;

if (l==r) 

{

tree[i]=0;

p=l;

return;

}

mid=(l+r)/2;

if (t<=tree[i*2]) insert(i*2,l,mid,t);

else insert(i*2+1,mid+1,r,t-tree[i*2]);

tree[i]=tree[i*2]+tree[i*2+1];

}

int main()

{

int n,m,i,j,sum,t,mm;

scanf("%d%d",&n,&m);

build(1,1,n);

p=0;t=n;

while(t)

{

sum=work(1,1,n,p+1,n);

mm=m;

while (mm>tree[1]) mm-=tree[1];

if (mm>sum) insert(1,1,n,mm-sum);

else insert(1,1,n,tree[1]-sum+mm);

printf("%d ",p);

--t;

}

printf("\n");

}

CODEVS1282 约瑟夫问题(线段树)