嘟嘟噜
题面
Description
由于众所周知的原因, 冈部一直欠真由理一串香蕉.
为了封上真由理的嘴, 冈部承诺只要真由理回答出这个问题, 就给她买一车的香蕉:
一开始有n 个人围成一个圈, 从1 开始顺时针报数, 报出m 的人被机关处决. 然后下一个人再从1 开始报数, 直到只剩下一个人.
红莉栖: “这不就是约瑟夫问题吗…”
伦太郎: “助手你给我闭嘴!”
真由理虽然已经晕头转向了, 但听到有一车的香蕉, 两眼便放出了光芒.
“那个呢, 真由氏很想要一车子的香蕉呢. 如果可以帮帮我的话, 我可以把一些香蕉分给你哟, 诶嘿嘿. 拜托你啦.”
Input
第一行一个整数T, 表示数据组数.
接下来T 行, 每行两个整数n;m.
Output
对于每组数据, 输出一行一个整数, 表示幸存者的编号.
Sample Input
5
4 6
2 8
2 9
8 8
7 9
Sample Output
3
1
2
4
7
Data Constraint
思路
这道题就是经典的约瑟夫问题。
方法一: O ( T n m ) O(Tnm) O(Tnm)
这种方法就是约瑟夫问题的纯暴力解法,在这里就不赘述了,可以自行研究一下。
方法二: O ( T n ) O(Tn) O(Tn)
为了方便我们研究我们把它的编号定为 0 0 0 ~ n − 1 n-1 n−1。
如果我们设第 k k k个人被踢出局(编号为 k − 1 k-1 k−1),则剩下的序列为:
k , k + 1 , k + 2 … n − 2 , n − 1 … 0 , 1 , 2 … k,k+1,k+2…n-2,n-1…0,1,2… k,k+1,k+2…n−2,n−1…0,1,2…
将序列整体减 k k k,并对负数加上 n n n:
0 , 1 , 2 … , n − 3 , n − 2 0,1,2…,n-3,n-2 0,1,2…,n−3,n−2
可以得出一个 n − 1 n-1 n−1阶的约瑟夫。由此我们可以得到一个递推,即:
a n s n = ( a n s n − 1 + k ) ( m o d n ) ans_n=(ans_{n-1}+k)\pmod{n} ansn=(ansn−1+k)(modn)
code
#include<cstdio>
#define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
int t,n,m;
int main()
{
// fre(mayuri);
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
scanf("%d%d",&n,&m);
int ans=0;
for(int j=1;j<=n;j++)
ans=(ans+m)%j;
printf("%d\n",ans+1);
}
}
方法三: O ( T m log ( n ) ) O(Tm\log(n)) O(Tmlog(n))
我们会发现其实在方法二中 a n s ans ans不一定大于 j j j,所以没有必要每次做。
我们知道 a n s ans ans每次加 m m m,而 j j j每次加1,问题就转换成:
已知: a n s + m ans+m ans+m, j + 1 j+1 j+1
求:什么时候 a n s ≥ j ans\ge j ans≥j
一个简单的追及问题, t = s v t=\frac sv t=vs,即 t = j − a n s m − 1 t=\frac {j-ans}{m-1} t=m−1j−ans,但由于整型向下取整,所以如果不能整除总数还要加一。
code
#include<cstdio>
#define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
int t,n,m;
int main()
{
// fre(mayuri);
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
scanf("%d%d",&n,&m);
int ans=0,k=1,p;
while(k<n)
{
p=(k-ans)/(m-1)+((k-ans)%(m-1)!=0);
if(k+p>n) p=n-k;
ans+=p*m;
k+=p;
ans%=k;
}
printf("%d\n",ans+1);
}
}