UVA1394 And Then There Was One

 题目链接

分析:

该题目是一个约瑟夫环的变形,区别就是第一个删除的数是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

代码参考:《算法竞赛入门经典——训练指南》

posted on 2013-04-28 13:19  Still_Raining  阅读(256)  评论(0编辑  收藏  举报