Joseph问题 (线段树)
Joseph问题似乎是入门题,就是那个报数出圈的问题,不过它暴力模拟的复杂度是O(nm)的,如果题目的数据范围达到了30000,那就超时了。怎么用线段树维护呢?
我们可以这么考虑,每次我们其实要查询在当前这个点过了m个人是哪一个人。我们需要维护一下当前序列中一共有多少人,还需要维护每个人实际的位置在哪(因为人们出圈了之后他就不占位置了)
我们可以用一棵权值线段树来完成。
首先是修改,这个没什么好说的,直接单点修改改成0就行,然后同时返回修改的位置,这是一个人出圈的位置。不过怎么找到这个位置呢?我们可以首先确定下来这个人在当前序列的第几位,那么我们直接在线段树上二分就可以了,然后返回那个位置。
一开始我们要query一下从1到上一次停留位置有几个人,然后把这个值+m-1,mod现在所有的人数再+1就是当前位置,找一下那个人在哪就可以了。
看一下代码。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<queue> #include<set> #define lowbit(x) x & (-x) #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') using namespace std; typedef long long ll; const int M = 60005; const ll INF = 100000000000009; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } struct seg { int v; } t[M<<2]; int n,m,last,g; void build(int p,int l,int r) { if(l == r) { t[p].v = 1; return; } int mid = (l+r) >> 1; build(p<<1,l,mid),build(p<<1|1,mid+1,r); t[p].v = t[p<<1].v + t[p<<1|1].v; } int query(int p,int l,int r,int pos) { if(l == r) return t[p].v = 0,l; int mid = (l+r) >> 1,cur; if(pos <= t[p<<1].v) cur = query(p<<1,l,mid,pos); else cur = query(p<<1|1,mid+1,r,pos-t[p<<1].v); t[p].v = t[p<<1].v + t[p<<1|1].v; return cur; } int count(int p,int l,int r,int kl,int kr) { if(kl > kr) return 0; if(l == kl && r == kr) return t[p].v; int mid = (l+r) >> 1; if(kr <= mid) return count(p<<1,l,mid,kl,kr); else if(kl > mid) return count(p<<1|1,mid+1,r,kl,kr); else return count(p<<1,l,mid,kl,mid) + count(p<<1|1,mid+1,r,mid+1,kr); } int main() { n = read(),m = read(); build(1,1,n); rep(i,1,n) printf("%d ",last = query(1,1,n,(count(1,1,n,1,last)+m-1)%t[1].v+1)); return 0; }
当你意识到,每个上一秒都成为永恒。