首页 > 代码库 > 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 约瑟夫问题(线段树)