第7回ブルーブリッジカップボールゲームの詳細
4474 ワード
ボールゲーム
二人でボールを取るゲームをします.
全部でN個のボールがあり、一人一人が交代でボールを取り、毎回集合{n 1,n 2,n 3}のいずれかの数を取ることができる.
ボールを取り続けることができなければ、ゲームは終了します.
この時、奇数球を持った方が勝つ.
2人とも奇数なら引き分けだ.
双方が最も賢い方法を採用していると仮定します.
最初にボールを取った人は必ず勝つことができますか?
この問題をプログラミングして解決する.
入力形式:
1行目の3つの正の整数n 1 n 2 n 3は、1回の取り得る数(0行目の5つの正の整数x 1 x 2...x 5は、5セットの初期球数(0
出力フォーマット:
1行5文字、スペースが分かれています.各セットで先にボールを取った人が勝つかどうかをそれぞれ表しています.
勝てば出力+,
次に、相手を追いつめる方法があれば、0を出力します.
どうしても負けたら出力-
たとえば、次のように入力します.
1 2 3
1 2 3 4 5
プログラムは出力するべきです:
+ 0 + 0 -
たとえば、次のように入力します.
1 4 5
10 11 12 13 15
プログラムは出力するべきです:
0 - 0++
たとえば、次のように入力します.
2 3 5
7 8 9 10 11
プログラムは出力するべきです:
+ 0 0 0 0
考え方:データ量を見て、動的な計画を行う必要があります.そうしないと、小さなデータしかできません.基本的な考え方は以下の通りです.
dp配列を定義し、現在順番になっているプレイヤー、Aプレイヤーの手球数、Bプレイヤーの手球数を格納する保存配列
Aは0、Bは1
現在触っている数が1,2,3ならボール総数は5
先手Aがボールを触る番になったら、今の状況は(0,0,0,5)、2番目と3番目のパラメータは今のAとBの手のボール数です.
Aは1、2、3を探して、触った後の局面を見て、例えば1つのボールを探して、それではこの局面(1、1、0、5)にジャンプするべきで、1はBプレイヤーの番で、Aはすでに触ったことがあって、Aは1つのボールを触ったので、2番目のパラメータは1です
最後に触ることができなければ,現在の局面を判断し,A%2,B%2はそれぞれAとBの手のボールのパリティを判断する.
コード:
まとめ:+、-は絶対性を持っていて、+はAが勝つことを代表して、-はAが負けることを代表します.
if (dfs(1, Aown + ns[i], Bown,totoalNum)=='+') {
return dp[0][Aown][Bown] = '+';
上のAはA(自分)が勝つ局面があるかどうかを探しています.
if (dfs(0, Aown , Bown+ ns[i],totoalNum)=='-') {
return dp[1][Aown][Bown] = '-';
このBは次のジャンプ局面でAを負ける局面、すなわちB(自分)が勝つ局面を探している.
A,Bは両方とも相手に負けたり、自分で勝つことができる局を探して、一つ見つけたらこのような取り方を採用して、見つからないなら和の局面があるかどうかを見て、二度と見つからないのは負けです
なぜ開局ごとにdp配列を再初期化しなければならないのか、前のデータは繰り返し使えないのか.
だめです.同じdp[0][1][0]でも始球数が違う場合は意味が違いますから.
クリアしなければ、現在の始局球数が5、Aが1つタッチした場合、直前に保持していた始局球数が1、Aが1つタッチした場合を呼び出す
後者は明らかに必勝局で,前者とは結末が違う.
二人でボールを取るゲームをします.
全部でN個のボールがあり、一人一人が交代でボールを取り、毎回集合{n 1,n 2,n 3}のいずれかの数を取ることができる.
ボールを取り続けることができなければ、ゲームは終了します.
この時、奇数球を持った方が勝つ.
2人とも奇数なら引き分けだ.
双方が最も賢い方法を採用していると仮定します.
最初にボールを取った人は必ず勝つことができますか?
この問題をプログラミングして解決する.
入力形式:
1行目の3つの正の整数n 1 n 2 n 3は、1回の取り得る数(0行目の5つの正の整数x 1 x 2...x 5は、5セットの初期球数(0
出力フォーマット:
1行5文字、スペースが分かれています.各セットで先にボールを取った人が勝つかどうかをそれぞれ表しています.
勝てば出力+,
次に、相手を追いつめる方法があれば、0を出力します.
どうしても負けたら出力-
たとえば、次のように入力します.
1 2 3
1 2 3 4 5
プログラムは出力するべきです:
+ 0 + 0 -
たとえば、次のように入力します.
1 4 5
10 11 12 13 15
プログラムは出力するべきです:
0 - 0++
たとえば、次のように入力します.
2 3 5
7 8 9 10 11
プログラムは出力するべきです:
+ 0 0 0 0
考え方:データ量を見て、動的な計画を行う必要があります.そうしないと、小さなデータしかできません.基本的な考え方は以下の通りです.
dp配列を定義し、現在順番になっているプレイヤー、Aプレイヤーの手球数、Bプレイヤーの手球数を格納する保存配列
Aは0、Bは1
現在触っている数が1,2,3ならボール総数は5
先手Aがボールを触る番になったら、今の状況は(0,0,0,5)、2番目と3番目のパラメータは今のAとBの手のボール数です.
Aは1、2、3を探して、触った後の局面を見て、例えば1つのボールを探して、それではこの局面(1、1、0、5)にジャンプするべきで、1はBプレイヤーの番で、Aはすでに触ったことがあって、Aは1つのボールを触ったので、2番目のパラメータは1です
最後に触ることができなければ,現在の局面を判断し,A%2,B%2はそれぞれAとBの手のボールのパリティを判断する.
コード:
import java.util.Scanner;
public class {
static int ns[] = new int[3];//
static int xs[] = new int[5];//
static char dp[][][] = new char[2][1000][1000];// , ,A ,B
//
public static int min(int a[]) {
int min=9999;
for (int i : a) {
if (min>i) {
min=i;
}
}
return min;
}
// ,A ,B ,
static char dfs(int p,int Aown,int Bown,int totoalNum){
int min = min(ns);//
int rest = totoalNum - Aown - Bown;// , -A -B
if (dp[p][Aown][Bown] != '\u0000') {//java char \u0000
return dp[p][Aown][Bown];// , ( ),
}
if (rest < min) {// 2 , 1 , ,
if (Aown % 2 == 1 && Bown % 2 == 0) {// A B , , + A
dp[p][Aown][Bown] = '+';
}
else if (Aown % 2 == 0 && Bown % 2 == 1) {//A
dp[p][Aown][Bown] = '-';
}else {
dp[p][Aown][Bown] = '0';
}
return dp[p][Aown][Bown];
}
if (p==0) {// A
boolean iseq = false;//
for (int i = 0; i < ns.length; i++) {//
if (rest >= ns[i]) { // , 2 , 1 , ,ns[i]
if (dfs(1, Aown + ns[i], Bown,totoalNum)=='+') {// , A , A
return dp[0][Aown][Bown] = '+';
}else if (dfs(1, Aown + ns[i], Bown,totoalNum) == '0') {// ,
iseq = true;
}
}
}
//for ,
if (iseq) // ,
dp[0][Aown][Bown] = '0';
else
dp[0][Aown][Bown] = '-';
}
if (p==1) {// B
boolean iseq = false;
for (int i = 0; i < ns.length; i++) {
if (rest >= ns[i]) { // , 2 , 1 , ,ns[i]
if (dfs(0, Aown , Bown+ ns[i],totoalNum)=='-') {// A , A ,
return dp[1][Aown][Bown] = '-';
}else if (dfs(0, Aown , Bown+ ns[i],totoalNum) == '0') {//
iseq = true;
}
}
}
// ,
if (iseq)
dp[1][Aown][Bown] = '0';
else
dp[1][Aown][Bown] = '+';
}
return dp[p][Aown][Bown];
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
for (int i = 0; i < ns.length; i++) {
ns[i] = scanner.nextInt();
}
for (int i = 0; i < xs.length; i++) {
xs[i] = scanner.nextInt();
}
int k;
for (int i = 0; i < xs.length; i++) {
dp = new char[2][1000][1000];// dp
k = dfs(0, 0, 0,xs[i]);
System.out.print((char)k + " ");
}
}
}
まとめ:+、-は絶対性を持っていて、+はAが勝つことを代表して、-はAが負けることを代表します.
if (dfs(1, Aown + ns[i], Bown,totoalNum)=='+') {
return dp[0][Aown][Bown] = '+';
上のAはA(自分)が勝つ局面があるかどうかを探しています.
if (dfs(0, Aown , Bown+ ns[i],totoalNum)=='-') {
return dp[1][Aown][Bown] = '-';
このBは次のジャンプ局面でAを負ける局面、すなわちB(自分)が勝つ局面を探している.
A,Bは両方とも相手に負けたり、自分で勝つことができる局を探して、一つ見つけたらこのような取り方を採用して、見つからないなら和の局面があるかどうかを見て、二度と見つからないのは負けです
なぜ開局ごとにdp配列を再初期化しなければならないのか、前のデータは繰り返し使えないのか.
だめです.同じdp[0][1][0]でも始球数が違う場合は意味が違いますから.
クリアしなければ、現在の始局球数が5、Aが1つタッチした場合、直前に保持していた始局球数が1、Aが1つタッチした場合を呼び出す
後者は明らかに必勝局で,前者とは結末が違う.