1. 程式人生 > >例題4-3--救濟金髮放

例題4-3--救濟金髮放

題目:
n(n<20)個人站成一圈,逆時針編號為1~n。 有兩個官員,A從1開始逆時針數,B從n開始順時針數。 在每一輪中,官員A數k個就停下來,官員B數m個就停下來(注意有可能兩個官員停在同一個人上)。 接下來被官員選中的人(1個或者2個)離開隊伍。輸入n,k,m輸出每輪裡被選中的人的編號(如果有兩個人,先輸出被A選中的)。 例如,n=10,k=4,m=3,輸出為4 8, 9 5, 3 1, 2 6, 10, 7。 注意:輸出的每個數應當恰好佔3列。

解析:
注意題目的描述及示例,可以看出:要想經過1號,就要從n號開始逆時針走;要想經過n號,就要從1號開始順時針走;因此有:p1=n;p2=1;
圖示如下:
在這裡插入圖片描述


對於環狀問題,首先想到的就是求餘的方法,假設起始點為p,那麼每移動一個點的原始的寫法是:

p=(p+1)%n;   //逆時針
p=(p-1)%n;   //順時針

這樣寫有沒有問題呢?我們需要考慮邊界條件。 分為兩種情況:
1.當逆時針移動時,考慮到n對n求餘等於0的時候(因為圈內沒有為0的點),此時p=(n-1);對應上面的示範為n=9。我們希望下一個元素求餘等於10----這顯然是不可能的,因為一個數對n求餘,其結果x一定滿足:0<=x<=(n-1)
如何解決這個問題呢?不妨讓求餘結果為(n-1),最終結果再加上1,就可以達到n了。
其實也就是用當前的值減一再對n進行求餘,最終結果+1。

表示式:

p=(p+d-1)%n+1;	//其中d代表正一或負一(順時針或者逆時針走)

2.當順時針移動時,仍然考慮邊界條件。當p=1時,移動後的下一個值應該到了10 。可是帶入資料卻發現餘數為0 。回顧逆時針的情況,最終的結果可以由餘9加1得到,餘9顯然在p=n-1時成立,因此表示式為:

p=(p+d+n-1)%n+1;

觀察上面兩個表示式,發現他們的差別僅僅是d的符號不同–這可以用傳不同引數來改變,以及差了一個週期n–對於求餘來說,加減n的倍數最終的結果是不改變的。

程式碼:

#include<iostream>
#include<Windows.h>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<ctype.h>
#include<cmath>
#define MAXN 10000
using namespace std;
int n, k, m, a[MAXN];
int go(int p,int d,int t) 
{
	while (t--)
	{
		do { p = (p + d + n - 1) % n + 1; } while (a[p] == 0);	//跳到非零的地方
	}
	return p;
}
int main()
{
	int i, j, p1, p2;
	int left;
	while (scanf("%d%d%d", &n, &k, &m) == 3 && n)
	{
		p1 = n;p2 = 1;
		left = n;
		for (i = 1;i <= n;i++) a[i] = i;
		while (left)
		{
			p1 = go(p1, +1, k);
			p2 = go(p2, -1, m);
			printf("%3d", p1); left--;
			if (p1 != p2) { printf("%3d", p2);left--; }
			a[p1] = a[p2] = 0;
			if (left) printf(",");
		}
		printf("\n");
	}
	system("pause");
	return 0;
}