题目链接。
分析:
该题目是一个约瑟夫环的变形,区别就是第一个删除的数是m。该题的n和k都比较大,链表法O(nk),是不行的。
因为只关心最后一个被删除的编号,而不需要完整的删除顺序,可以用递推法求解。
先分析一下传统的约瑟夫环:n个人,编号0~n-1,每喊道k该人就被淘汰,直到最后剩下一个人。
公式为:f(n) = (f(n-1) + k) % n,f(1)=0(这里的f(n)为最后一个人在序列剩余n个人时的编号)
公式推导:
初始的序号为
(一)0, 1, ..., q-1, q, q+1, ..., n-1
设q = k % n, 出列q-1后,序列变为
(二)0, 1, ..., q-2, q, q+1, ..., n-1
延长这个序列,可以得到
(三)0, 1, ..., q-2, q, q+1, ..., n-1, n, n+1, ..., n+q-2
从q开始取,直到n-q+2, 整个序列都减去q,得到
(四)0, 1, ..., n-2
经过(一)(二)(三)(四)完成了n序列到n-1序列的变化。那么这究竟是什么意思呢。仔细看下,(四)的任意元素,是不是都能找到在初始序列中的位置?
例如(四)中的0在初始的序列编号为q。怎么找出的呢?倒着推:即(0+q)%n
用如此的方法,即,每删除一个元素,就从它后面的一个元素开始重新编号,那么当序列中只存在一个元素时(即数完n-1次),序列的唯一元素为0.
我们用上面的方法我们能够找出该元素在数完n-2次序列中的标号,数完n-3次数列中的标号,直到初始的序号。
即f(i) = (f(i-1) + k) % i
如果编号是从1~n开始的,别忘了处理下:
f[1]=1; f[i]=(f[i-1]+m)%i (i>1); if(f[i]==0) f[i]=i;
或者按着从0~n-1编号,最后加1, 即(f(n)+1)%n
推广到第一次删除m,想象成一个编号的大圆盘,逆时针旋转m-k次,每次移动一个编号。
本题的AC代码(参考自《训练指南》):
#include <iostream> #include <algorithm> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; const int maxn = 10000 + 10; int f[maxn]; int main(){ int n, k, m; while(scanf("%d%d%d", &n, &k, &m) == 3){ if(n == 0 && k == 0 && m == 0) break; f[1] = 0; for(int i=2; i<=n; i++) f[i] = (f[i-1]+k)%i; int ans = (m-k+f[n]+1) % n; if(ans <= 0) ans += n; printf("%d\n", ans); } return 0; }
公式推导参考自:http://www.bitsucker.com/archives/7
代码参考:《算法竞赛入门经典——训练指南》