《算法竞赛·快冲300题》每日一题:“石头剪刀布 IV”

算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。

文章目录

  • 题目描述
  • 题解
  • C++代码
    • (1)不用滚动数组
    • (2)修改check()函数
    • (3)交替滚动数组的一种实现
    • (4)交替滚动数组的另一种实现
  • Java代码
  • Python代码

石头剪刀布 IV” ,链接: http://oj.ecustacm.cn/problem.php?id=1771

题目描述

【题目描述】 小蓝和小红在玩石头剪刀布的游戏,为方便描述,分别用数字1、2、3表示石头、剪刀、布。
游戏进行N轮,小蓝已经知道小红每一轮要出的手势,但是小蓝很懒,最多变换K次手势。
请求出小蓝最多赢的次数。。
【输入格式】 第一行输入两个数字N和K。(1≤N≤100000,0≤K≤20)
第二行输入N个数字表示小红每一轮输出的手势。。
【输出格式】 输出一个数字表示答案。。
【输入样例】

5 1
3 3 1 3 2

【输出样例】

4

题解

   读者可以试试用暴力或贪心是否能够求解。当贪心不能得到全局最优解时,常常能用DP求解。
   定义状态dp[i][j][k],表示小蓝前i轮变换了j次,当前手势为k的最大的胜利次数。计算结束后,答案是max{dp[n][k][1], dp[n][k][2],dp[n][k][3]}。
   下面推导状态转移方程。考察小蓝的连续2次手势,有2种情况:不变、变了。
   (1)手势不变。那么变换次数j没有变。
   小蓝是否能赢?设小蓝的上一次手势为k,新手势为nk,由于手势不变有nk = k。nk能否赢a[i]?用check(nk, a[i])判断能赢的几种情况即可。状态转移方程的代码这样写:
      dp[i][j][nk] = max(dp[i][j][nk], dp[i-1][j][k] + check(nk,a[i]));
   (2)手势变了。那么dp[i][j][]从dp[i-1][j-1][]递推而来。同样用check(nk, a[i])判断能赢的几种情况。状态转移方程的代码:
      dp[i][j][nk] = max(dp[i][j][nk], dp[i-1][j-1][k] + check(nk,a[i]));
【重点】 动态规划、滚动数组。

C++代码

(1)不用滚动数组

   下面先给出不用滚动数组的代码。第6行定义状态int dp[100010][25][4],使用空间40M。

#include
using namespace std;
int dp[100010][25][4];                              
int a[100010];
int check(int a, int b) {                           //几种赢的情况
    if (a == 1 && b == 2) return 1;
    if (a == 2 && b == 3) return 1;
    if (a == 3 && b == 1) return 1;
    return 0;
}
int main(){
    int N, K; cin >> N >> K;
    K += 1;
    for(int i = 1; i <= N; i++)    cin >> a[i];
    for(int i = 1; i <= N; i++)     {          //n次游戏
        for(int j = 1; j <= K; j++)            //变换的次数
            for(int k = 1; k <= 3; k++){         //小蓝上一轮的手势
                for(int nk = 1; nk <= 3; nk++){  //小蓝当前的手势
                    if(k == nk)                  //手势不需要变化,那么j不变
                        dp[i][j][nk] = max(dp[i][j][nk], dp[i-1][j][k] + check(nk,a[i]));
                    else                          //手势需要变化,那么j减1
                        dp[i][j][nk] = max(dp[i][j][nk], dp[i-1][j-1][k] + check(nk,a[i]));
                }
            }
    }
    cout<<max(dp[N][K][1], max(dp[N][K][2], dp[N][K][3]))<<endl;
    return 0;
}

(2)修改check()函数

其中的check()函数,可以简化为:nk == a[i] % 3 + 1。重写代码如下:

#include
using namespace std;
int dp[100010][25][4];
int a[100010];
int main(){
    int N, K; cin >> N >> K;
    K += 1;
    for(int i = 1; i <= N; i++)       cin >> a[i];
    for(int i = 1; i <= N; i++)     {          //n次游戏
        for(int j = 1; j <= K; j++)            //变换的次数
            for(int k = 1; k <= 3; k++){         //小蓝上一轮的手势
                for(int nk = 1; nk <= 3; nk++){  //小蓝当前的手势
                    if(k == nk)                  //手势不需要变化,那么j不变
                        dp[i][j][nk] = max(dp[i][j][nk], dp[i-1][j][k] + (nk == a[i] % 3 + 1));
                    else                          //手势需要变化,那么j减1
                        dp[i][j][nk] = max(dp[i][j][nk], dp[i-1][j-1][k] + (nk == a[i] % 3 + 1));
                }
            }
    }
    cout<<max(dp[N][K][1], max(dp[N][K][2], dp[N][K][3]))<<endl;
    return 0;
} 

(3)交替滚动数组的一种实现

可以用滚动数组优化空间。下面给出交替滚动的一种实现:用i%2和(i-1)%2交替滚动。第3行定义状态int dp[2][25][4],使用空间800字节。

#include
using namespace std;
int dp[2][25][4];                     //滚动数组                           ///滚动数组
int a[100010];
int main(){
    int N, K; cin >> N >> K;
    K += 1;
    for(int i = 1; i <= N; i++)       cin >> a[i];
    for(int i = 1; i <= N; i++)     {          //n次游戏
        for(int j = 1; j <= K; j++)            //变换的次数
            for(int k = 1; k <= 3; k++){         //小蓝上一轮的手势
                for(int nk = 1; nk <= 3; nk++){  //小蓝当前的手势
                    if(k == nk)                  //手势不需要变化,那么j不变
                         //当前手势为k,能否赢a[i]:只需要判断当前手势编号是否等于之前的编号即可
                        dp[i%2][j][nk] = max(dp[i%2][j][nk], dp[(i-1)%2][j][k] + (nk == a[i] % 3 + 1));
                    else                          //手势需要变化,那么j减1
                        dp[i%2][j][nk] = max(dp[i%2][j][nk], dp[(i-1)%2][j-1][k] + (nk == a[i] % 3 + 1));
                }
            }
    }
    cout<<max(dp[N%2][K][1], max(dp[N%2][K][2], dp[N%2][K][3]))<<endl;
    return 0;
} 

(4)交替滚动数组的另一种实现

下面是“交替滚动”的另一种实现,见《算法竞赛》P323页“交替滚动”:用old和now交替滚动。

#include
using namespace std;
int dp[2][25][4];                     //滚动数组       
int a[100010];
int main(){
    int N, K; cin >> N >> K;
    K += 1;
    for(int i = 1; i <= N; i++)       cin >> a[i];
    int now = 0,old = 1;                     //交替滚动
    for(int i = 1; i <= N; i++)     {          //n次游戏
        swap(old,now);
        for(int j = 1; j <= K; j++)            //变换的次数
            for(int k = 1; k <= 3; k++){         //小蓝上一轮的手势
                for(int nk = 1; nk <= 3; nk++){  //小蓝当前的手势
                    if(k == nk)                  //手势不需要变化,那么j不变
                         //当前手势为k,能否赢a[i]:只需要判断当前手势编号是否等于之前的编号即可
                        dp[now][j][nk] = max(dp[i%2][j][nk], dp[old][j][k] +(nk == a[i] % 3 + 1));
                    else                          //手势需要变化,那么j减1
                        dp[now][j][nk] = max(dp[i%2][j][nk], dp[old][j-1][k] + (nk == a[i] % 3 + 1));
                }
            }
    }
    cout<<max(dp[now][K][1], max(dp[now][K][2], dp[now][K][3]))<<endl;
    return 0;
}  

本题的滚动数组能否用“自我滚动”?请读者思考。

Java代码

import java.util.*;
public class Main {
    static int[][][] dp = new int[2][25][4]; // 滚动数组
    static int[] a = new int[100010];    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int K = scanner.nextInt();
        K += 1;
        for (int i = 1; i <= N; i++)   a[i] = scanner.nextInt();        
        int now = 0, old = 1; // 交替滚动
        for (int i = 1; i <= N; i++) { // n次游戏
            int temp = now;
            now = old;
            old = temp;
            for (int j = 1; j <= K; j++) { // 变换的次数
                for (int k = 1; k <= 3; k++) { // 小蓝上一轮的手势
                    for (int nk = 1; nk <= 3; nk++) { // 小蓝当前的手势
                        if (k == nk) { // 手势不需要变化,那么j不变                            
                            dp[now][j][nk] = Math.max(dp[now][j][nk], dp[old][j][k] + (nk == a[i] % 3 + 1 ? 1 : 0));
                        } else { // 手势需要变化,那么j减1
                            dp[now][j][nk] = Math.max(dp[now][j][nk], dp[old][j-1][k] + (nk == a[i] % 3 + 1 ? 1 : 0));
                        }
                    }
                }
            }
        }
        System.out.println(Math.max(dp[now][K][1], Math.max(dp[now][K][2], dp[now][K][3])));
    }
}

Python代码

   。

N, K = map(int, input().split())
K += 1
a = [0] + list(map(int, input().split()))
dp = [[[0 for _ in range(4)] for _ in range(25)] for _ in range(2)] # 滚动数组
old, now = 0, 1                       # 交替滚动
for i in range(1, N + 1):             # n次游戏
    old, now = now, old
    for j in range(1, K + 1):         # 变换的次数
        for k in range(1, 4):         # 小蓝上一轮的手势
            for nk in range(1, 4):    # 小蓝当前的手势
                if k == nk:           # 手势不需要变化,那么j不变
                    dp[now][j][nk] = max(dp[now][j][nk], dp[old][j][k] + (nk == a[i] % 3 + 1))
                else: #              手势需要变化,那么j减1
                    dp[now][j][nk] = max(dp[now][j][nk], dp[old][j-1][k] + (nk == a[i] % 3 + 1))
print(max(dp[now][K][1], dp[now][K][2], dp[now][K][3]))

你可能感兴趣的:(算法竞赛快冲300题,算法,动态规划)