P8671 [蓝桥杯 2018 国 AC] 约瑟夫环 题解
_fairytale_ · · 题解
大家好,我非常喜欢线段树,所以我用线段树通过了这道题。
看各位大佬们用的都是递推公式,本蒟蒻就讲一个好理解一点的线段树吧。
朴素做法当然是直接枚举,时间复杂度 \mathcal O(nk) 。
我们现在想一下它的问题在哪里。每次踢一个人没有问题,但是查找下一个要被踢掉的人要用
我们的线段树要支持以下操作:
-
单点修改(删除)
-
查询区间中第
k 个没被删除的点
第一个操作好说,第二个其实有做法,但是有点科技,这里讲一个小技巧转化成全局查询。
我们先把没被踢的人记为
这样,我们便可以
设当前被删的人的位置为
接下来分类讨论一下:
全局查询直接在线段树上二分就可以。
这样就做完啦,有什么问题欢迎指出!
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
const int maxn=1e6+10;
int n,k;
int t[maxn<<2];
inline void pushup(int p){
t[p]=t[ls]+t[rs];
}
void build(int p,int l,int r){
if(l==r){
t[p]=1;
return ;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(p);
}
void remove(int p,int l,int r,int v){
if(l==r){
t[p]=0;
return ;
}
if(mid>=v)remove(ls,l,mid,v);
else remove(rs,mid+1,r,v);
pushup(p);
}
int querys(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)return t[p];
int res=0;
if(mid>=L)res+=querys(ls,l,mid,L,R);
if(mid<R)res+=querys(rs,mid+1,r,L,R);
return res;
}
int queryr(int p,int l,int r,int rk){
if(l==r)return l;
if(t[ls]>=rk)return queryr(ls,l,mid,rk);
else return queryr(rs,mid+1,r,rk-t[ls]);
}
int pos=0;
int rest;
bool killed[maxn];
int tmp,pre,suf;
int main() {
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>k;
rest=n;
build(1,1,n);
while(rest>1){
tmp=(k-1)%rest+1;//随着剩余人数的减少,k有可能比n大,需要处理一下
pre=(pos<=1?0:querys(1,1,n,1,pos-1));
suf=querys(1,1,n,pos+1,n);
if(suf<tmp)pos=queryr(1,1,n,tmp-suf);
else pos=queryr(1,1,n,pre+tmp);
remove(1,1,n,pos);
killed[pos]=true;
rest--;
}
for(re int i=1;i<=n;++i){
if(!killed[i]){
cout<<i;
break;
}
}
return 0;
}