1. 程式人生 > >華為機試 之 joseph環

華為機試 之 joseph環

一:首先科普一下約瑟夫問題的數學方法 

      (1)  無論是用list實現還是用vector實現都有一個共同點:要模擬整個遊戲過程,不僅程式寫起來比較煩,而且時間複雜度高達O(nm),當n,m非常大(例如上百萬,上千萬)的時候,幾乎是沒有辦法在短時間內出結果的。我們注意到原問題僅僅是要求出最後的勝利者的序號,而不是要讀者模擬整個過程。因此如果要追求效率,就要打破常規,實施一點數學策略。  

 (2) 為了討論方便,先把問題稍微改變一下,並不影響原意:  問題描述:n個人(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。 我們知道第一個人(編號一定是m%n-1) 出列之後,剩下的n-1個人組成了一個新的約瑟夫環(以編號為k=m%n的人開始):  k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2  並且從k開始報0。     現在我們把他們的編號做一下轉換:  k --> 0  k+1 --> 1  k+2 --> 2  ...  ...  k-2 --> n-2  k-1 --> n-1  變換後就完完全全成為了(n-1)個人報數的子問題

(3)假如我們知道這個子問題的解:例如x是最終的勝利者,那麼根據上面這個表把這個x變回去不剛好就是n個人情況的解嗎?!變回去的公式很簡單,相信大家都可以推出來:x'=(x+k)%n  如何知道(n-1)個人報數的問題的解?對,只要知道(n-2)個人的解就行了。(n-2)個人的解呢?當然是先求(n-3)的情況 ---- 這顯然就是一個倒推問題!好了,思路出來了,下面寫遞推公式: 

 令f[i]表示i個人玩遊戲報m退出最後勝利者的編號,最後的結果自然是f[n]  ;遞推公式 ; f[1]=0;  f[i]=(f[i-1]+m)%i; (i>1)  

有了這個公式,我們要做的就是從1-n順序算出f[i]的數值,最後結果是f[n]。因為實際生活中編號總是從1開始,我們輸出f[n]+1  由於是逐級遞推,不需要儲存每個f[i],程式也是異常簡單。

二:原題再現:

(1)The Joseph's problem is notoriously known. For those who are not familiar with the original problem: from among n people, numbered 1, 2, . . ., n, standing in circle every mth is going to be executed and only the life of the last remaining person will be saved. Joseph was smart enough to choose the position of the last remaining person, thus saving his life to give us the message about the incident. For example when n = 6 and m = 5 then the people will be executed in the order 5, 4, 6, 2, 3 and 1 will be saved. 

       Suppose that there are k good guys and k bad guys. In the circle the first k are good guys and the last k bad guys. You have to determine such minimal m that all the bad guys will be executed before the first good guy. 

(2)分析:本題是約瑟夫環變形 先引入Joseph遞推公式,設有n個人(0,...,n-1),數m,則第i輪出局的人為f(i)=(f(i-1)+m-1)%(n-i+1),f(0)=0; f(i) 表示當前子序列中要退出的那個人(當前序列編號為0~(n-i));
拿個例子說:K=4,M=30;
f(0)=0;
f(1)=(f(0)+30-1)%8=5; 序列(0,1,2,3,4,5,6,7)中的5
f(2)=(f(1)+30-1)%7=6; 序列(0,1,2,3,4,6,7)中的7
f(3)=(f(2)+30-1)%6=5; 序列(0,1,2,3,4,6)中的6
f(4)=(f(3)+30-1)%5=4; 序列(0,1,2,3,4)中的4
........


依據題意,前K個退出的人必定是後K個人,所以只要前k輪中只要有一次f(i)<k則此m不符合題意。
接下來說說m的取值範圍:我們考察一下只剩下k+1個人時候情況,即壞人還有一個未被處決,那麼在這一輪中結束位置必定在最後一個壞人,那麼開始位置在哪呢?這就需要找K+2個人的結束位置,然而K+2個人的結束位置必定是第K+2個人或者第K+1個人,這樣就出現兩種順序情況:GGGG.....GGGXB 或  GGGG......GGGBX (X表示有K+2個人的那一輪退出的人)所以有K+1個人的那一輪的開始位置有兩種可能即第一個位置或K+1的那個位置,限定m有兩種可能:t(k+1) 或 t(k+1)+1; t>=1; 若遍歷每一個m必定超時,避免超時則需要打表和限制m的範圍。

(3) 程式碼如下:

using namespace std;
const int MAX_SIZE = 14;

int main(void)
{
	int joseph[MAX_SIZE]={0};  //打表,儲存各個k值對應的m值

	int k;
	int i;
	while(cin>>k)
	{
		if(0 == k)
			break;
		if(joseph[k])// 此k值已經求過直接列印,並下一次迴圈
		{
			cout<<joseph[k]<<endl;
			continue;
		}

		int n=2*k;  //總人數
		int ans[30]={0};  //第i輪殺掉 對應當前輪的編號為ans[i]的人
		                  //PS:每一輪都以報數為“1”的人開始重新編號
                        // 例如ans[1] = 3 表示第一次把下標為3的人殺掉(其實是第四個人)
		int m=k+1;    //所求的最少的報數,從k+1開始,因為
        for(i=1;i<=k;i++)
        {
            ans[i] = (ans[i-1]+m-1)%(n-i+1);
            //cout << ans[i] << ",";// 殺人的原始陣列的下標,也相當於erase過程,因為要遷移
            //如果應用vector的vec[i],vec.erase(iter)就可以實現殺人順序
            if(ans[i] < k)// 不符合殺人條件
            {
                i = 0;
                m++;// m加一,從i=1再次殺人
            }
        }
        joseph[k] = m;
        cout << joseph[k] << endl;
	}
	return 0;
}//poj 1012

三:問題延伸:(更簡單的問題) (1)有N個小孩圍成一圈,給他們從1開始依次編號,現指定從第W個開始報數,報到第S個時,該小孩出列,然後從下一個小孩開始報數,仍是報到S個出列,如此重複下去,直到所有的小孩都出列(總人數不足S個時將迴圈報數),求小孩出列的順序。 第一行輸入小孩的人數N(N<=64) 
接下來每行輸入一個小孩的名字(人名不超過15個字元) 
最後一行輸入W,S (W < N),用逗號","間隔 (2)poj 3750的程式碼,用list和vector分別實現:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int MAX_SIZE = 20;

int main()
{
    int n,w,s;
    int i,cp;
    char str[MAX_SIZE];
    vector<string> vec;
    scanf("%d",&n);
    for(i=0;i<n;i++)
    {
        cin >> str;
        vec.push_back(str);
    }
    scanf("%d,%d",&w,&s);
    vector<string>::iterator iter;
    cp = 1;
    for(iter=vec.begin()+w-1; vec.begin()!=vec.end();)
    {
        cp++;
        if(iter == vec.end())
            iter = vec.begin();
        if(cp<=s)
        {
            iter++;
        }
        else
        {
            cout << *iter << endl;
            iter = vec.erase(iter);
            //iter++;// 因為刪除iter,自動指向下一個元素的;
            cp = 1;
        }
    }

    return 0;
}//poj 3750
*/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <list>
using namespace std;
const int MAX_SIZE = 20;

int main()
{
    int n,w,s;
    int i,cp;
    char str[MAX_SIZE];
    list<string> lists;
    scanf("%d",&n);
    for(i=0;i<n;i++)
    {
        cin >> str;
        lists.push_back(str);
    }
    scanf("%d,%d",&w,&s);
    list<string>::iterator iter;
	iter = lists.begin();
	while(w>1)
	{
		iter++;
		w--;
	}
	cp = 1;
    for(; lists.size()!=0;)
    {
        cp++;
        if(iter == lists.end())
            iter = lists.begin();
        if(cp<=s)
        {
            iter++;
        }
        else
        {
            cout << *iter << endl;
            iter = lists.erase(iter);// 因為刪除iter,自動返回下一個元素的,vector也是一樣;否則ter成為野指標
            cp = 1;
        }
    }
    return 0;
}//poj 3750



相關推薦

joseph

一:首先科普一下約瑟夫問題的數學方法        (1)  無論是用list實現還是用vector實現都有一個共同點:要模擬整個遊戲過程,不僅程式寫起來比較煩,而且時間複雜度高達O(nm),當n,m非常大(例如上百萬,上千萬)的時候,幾乎是沒有辦法在短時間內出結果的。我們

牛客網三道程式設計題(五) 線上訓練(四)進位制轉換、刪除重複出現的數字、句子逆序

1、進位制轉換       這一題利用 cout 的流輸出,非常簡單。不需要再根據下標啥的,再乘以16 或者除以16啥的。請注意:       1)利用 cout 流輸出的,都是字串,不管它是不是數字。       2)cout 預設輸出的是十進位制。此外,如果是八進位制,

線上訓練》計算字元個數

寫出一個程式,接受一個有字母和數字以及空格組成的字串,和一個字元,然後輸出輸入字串中含有該字元的個數。不區分大小寫。 輸入描述: 輸入一個有字母和數字以及空格組成的字串,和一個字元。 輸出描述: 輸出輸入字串中含有該字元的個數。 輸入例子: ABCDEF A 輸出例子: 1 解題思路:第一反應就是可以

[練習題]2.大數求和

cout views 華為上機 大數 高位到低位 pretty 類別 ade iostream 題目二[大數求和] 描寫敘述: 給定兩個非常大的正整數A和B。位數在50至100之間。求C=A+B; 題目類別: 字符串 難度: 中級

2014(一)

ack cpp string.h ont const 機試題 輸出字符串 abcd 函數 2014華為機試(一)/****************************************************************************

-字符串匹配

輸入 scanner i++ -m 實現 ret 字符 als num 題目描述 題目標題: 判斷短字符串中的所有字符是否在長字符串中全部出現 詳細描述: 接口說明 原型: boolIsAllCharExist(char* pShortString,char* pLo

-密碼強度等級

java程序 strong while oid ont || ascii wds tro 題目描述 密碼按如下規則進行計分,並根據不同的得分為密碼進行安全等級劃分。 一、密碼長度: 5 分: 小於等於4 個字符 10 分: 5 到

[練習題]56.求子數組的最大和

== process ack turn 多個 popu 基礎 ace ava 題目 描寫敘述: 輸入一個整形數組。數組中連續的一個或多個整數組成一個子數組,每一個子數組都有一個和。求全部子數組的和的最大值。 接口 Int GetSub

—變形的楊輝三角形

mar ios alt mark word http processor string tex 變形的楊輝三角形 每一個數是上行,左右3個數之和,不存在為0,求第n行第一個偶數出現的位置,沒有輸出-1。 #include<iostrea

】找最高分(通過此題熟悉牛客網Node輸入輸出)

length 輸出 ons ken [0 接下來 lin tput int 來源:牛客網 老師想知道從某某同學當中,分數最高的是多少,現在請你編程模擬老師的詢問。當然,老師有時候需要更新某位同學的成績. 輸入描述: 輸入包括多組測試數據。每組輸入第一行是兩個正整數N和M(0

:字串的連接最長路徑查找

too 小寫 while arrays 空間 i++ har cap over 這個題更應該叫做字符串字典序排序 題目描述 給定n個字符串,請對n個字符串按照字典序排列。 輸入描述: 輸入第一行為一個正整數n(1≤n≤1000),下面n行為n個字符串

:汽水瓶

方法 new code imp subject tint 表示 next quest 題目描述 有這樣一道智力題:“某商店規定:三個空汽水瓶可以換一瓶汽水。小張手上有十個空汽水瓶,她最多可以換多少瓶汽水喝?”答案是5瓶,方法如下:先用9個空

】—— 6.質數因子

string ner out system pre 分享 args 機試 分享圖片 題目 解法 import java.util.Scanner; public class Main { public static void main(Strin

練習

ace tor 排序 技術分享 length a-z string it! 計算 1. 計算字符個數 輸入 ABCDEF A 輸出 1 #include <iostream> #include <cstring> using n

提取不重復的整數

des back 重復 mes 提取 push_back pan i++ -h 題目描述 輸入一個int型整數,按照從右向左的閱讀順序,返回一個不含重復數字的新的整數。 輸入描述: 輸入一個int型整數 輸出描述: 按照從右向左的閱讀順序,返回一個不含重復數字的新的整

牛客網 - 線上程式設計 - - 合併表記錄

題目描述 資料表記錄包含表索引和數值,請對錶索引相同的記錄進行合併,即將相同索引的數值進行求和運算,輸出按照key值升序進行輸出。 輸入描述: 先輸入鍵值對的個數 然後輸入成對的index和value值,以空格隔開 輸出描述: 輸出合併後的鍵值對(多

牛客網 - 線上程式設計 - - 字串字典順序排序

題目描述 給定n個字串,請對n個字串按照字典序排列。 輸入描述: 輸入第一行為一個正整數n(1≤n≤1000),下面n行為n個字串(字串長度≤100),字串中只含有大小寫字母。 輸出描述: 資料輸出n行,輸出結果為按照字典序排列的字串。 示例1

牛客網 - 線上程式設計 - - 刪除字串中出現次數最少的字元

題目描述 實現刪除字串中出現次數最少的字元,若多個字元出現次數一樣,則都刪除。輸出刪除這些單詞後的字串,字串中其它字元保持原來的順序。 輸入描述: 字串只包含小寫英文字母, 不考慮非法輸入,輸入的字串長度小於等於20個位元組。 輸出描述: 刪除字串中出現

牛客網 - 線上程式設計 - - 單詞倒排

題目描述 對字串中的所有單詞進行倒排。 說明: 1、每個單詞是以26個大寫或小寫英文字母構成; 2、非構成單詞的字元均視為單詞間隔符; 3、要求倒排後的單詞間隔符以一個空格表示;如果原字串中相鄰單詞間有多個間隔符時,倒排轉換後也只允許出現一個空格間隔符; 4、每個單詞最

牛客網 - 線上程式設計 - - 字串處理

題目描述 按照指定規則對輸入的字串進行處理。 詳細描述: 將輸入的兩個字串合併。 對合並後的字串進行排序,要求為:下標為奇數的字元和下標為偶數的字元分別從小到大排序。這裡的下標意思是字元在字串中的位置。 對排序後的字串進行操作,如果字元為‘0’——‘9’或者‘A’——‘F’