引入
约瑟夫问题是一个经典问题:
有 n n n个人排成一个环,第1个人从1开始报数,如果报到 k k k的倍数那么出列,让你求出出列顺序/最后剩下谁.
暴力
我们用链表维护相连关系,那么可以
O
(
n
k
)
O(nk)
O(nk)求解.
但是复杂度实在过高了.
d p dp dp优化
这个只能求最后剩余的人.
为了方便我们令编号从0开始.
设
f
[
i
]
f[i]
f[i]表示长度为
i
i
i的约瑟夫问题最后剩下的编号.
那么有
f
[
n
]
=
(
f
[
n
−
1
]
+
k
)
m
o
d
n
f[n]=(f[n-1]+k)\mod n
f[n]=(f[n−1]+k)modn.
因为当前局面删除
k
−
1
k-1
k−1,我们给
k
k
k编号0,
k
+
1
k+1
k+1编号1…
那么转化一下就是上面的求法啦.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int n,k;
cin >> n >> k;
int ans=0;
for(int i=1;i<=n;i++) (ans += k) %= i;
printf("%d\n",ans+1);
return 0;
}
数据结构优化
这是在 O ( n log n ) O(n\log n) O(nlogn)下求出整个序列的做法.
题目:
求 x x x次 k − k- k−约瑟夫变化后的序列.
我们记
p
o
s
pos
pos为上一个删除的元素的排名,初始
p
o
s
=
0
pos=0
pos=0.
那么对于第
i
i
i次操作,我们就要找
(
p
o
s
−
1
+
k
−
1
)
%
(
n
−
i
+
1
)
+
1
(pos-1+k-1)\%(n-i+1)+1
(pos−1+k−1)%(n−i+1)+1大的位置.
用树状数组维护一下合法位置的前缀个数即可.
然后用快速幂结合一下置换即可.
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,f[N],g[N],a[N],c[N];
void qr(int &x) {scanf("%d",&x); }
void add(int x,int d) {for( ;x<=n;x+=x&-x) c[x]+=d;}
int ask(int k) {
int y=0;
for(int i=18;i>=0;i--)
if(y+(1<<i)<=n&&c[y+(1<<i)]<k)
y+=1<<i,k-=c[y];
return y+1;
}
int main() {
qr(n); qr(m);
for(int i=1;i<=n;i++) a[i]=i;
while(m--) {
int k,x; qr(k); qr(x);
for(int i=1;i<=n;i++) c[i]=i&-i;
int pos=1;
for(int i=1,j;i<=n;i++)
f[j=ask(pos=(pos-1+k-1)%(n-i+1)+1)]=i,
add(j,-1);
while(x) {
if(x&1) {
for(int i=1;i<=n;i++) c[f[i]]=a[i];
for(int i=1;i<=n;i++) a[i]=c[i];
}
for(int i=1;i<=n;i++) g[i]=f[f[i]];
for(int i=1;i<=n;i++) f[i]=g[i];
x /= 2;
}
}
for(int i=1;i<=n;i++) printf("%d ",a[i]);
return 0;
}
n n n较大, k k k较小的情况
题目
观察上面的DP方程.
f
[
i
]
=
(
f
[
i
−
1
]
+
k
)
m
o
d
n
f[i]=(f[i-1]+k)\mod n
f[i]=(f[i−1]+k)modn.
实际上
m
o
d
n
\mod n
modn只有在
f
[
i
−
1
]
+
k
≥
n
f[i-1]+k\ge n
f[i−1]+k≥n的情况
m
o
d
n
\mod n
modn发挥作用.
所以我们用追及问题的方法求即可.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k,t,ans,s;
int main() {
cin>>n>>k;
while(s<n) {
ll t=min((s-ans-1)/(k-1)+1,n-s); s += t;
ans=(ans+t*k)%s;
}
cout<<ans+1;
}
可以发现 k k k次就能达到原来的两倍,所以复杂度为 O ( k log n ) O(k\log n) O(klogn).
如果我们推广到 lg n ≤ 100 \lg n\le 100 lgn≤100也是可做的,就是高精度取 min \min min 要一个 100 100 100的常数罢了.