《挑战程序设计竞赛》4.2.1 游戏必胜策略-推理与动态规划算法 POJ2484 2348 1082 2068 3688 1740(1)

POJ 2484 取硬币游戏

http://poj.org/problem?id=2484

题意

给出N个硬币围成一个圈,然后两个人从这圈硬币中轮流拿1个或毗邻的2个硬币。直到全部拿完为止,最后一个拿的人为,胜者。

思路

当n==1 || n==2时,明显先手必胜。
当n==3时,明显先手必败。
由于每次只可取1或2个,而取2个时,2个必须相邻,推断有:
当n>3时,若n为偶数,先手无论如何取,后手可在先手对称的位置上取同等数量,于是先手必败。
若n为奇数,先手取1个时,后手可在先手对称的位置上取2个,之后无论先手如何取,后手都可在先手对称的位置上取同等数量,先手必败。
如果先手一开始取2个时,后手可在先手对称的位置上取1个,之后还剩下偶数个,可如上推出先手必败。故当 n>3时,先手必败。

代码

Source Code

Problem: 2484       User: liangrx06
Memory: 236K        Time: 0MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

int main(void)
{
    int n;
    while (cin >> n && n) {
        if (n <= 2) printf("Alice\n");
        else printf("Bob\n");
    }

    return 0;
}

POJ2348 欧拉游戏

http://poj.org/problem?id=2348

题意

给两堆石子(题目中是数,配合一下上文这里说石子),两人依次取石子,规则是:每次从石子数较多的那堆取(两堆石子数目相等时任选一堆),取的数目只能为石子少的那一堆的正整数倍。
最后取完一堆石子者胜。问给定情况下先手胜负情况。

思路

书中有详细的讲解,假设a>b,若a%b == 0或a-b > b则必胜,否则只能取a-b,然后看后续情况。
但这个题困扰我的问题是循环条件。这个能AC代码的循环判断条件是:
while (a%b && a-b < b)
而我最开始WA的代码中的判断条件是:
while (a-b < b)
判断条件之前已经对a和b的值进行过处理使a>=b,而且题目中说明a和b都是正数。那么这两个判断条件难道效果不是一样的吗?另外这样写也不会有溢出危险,除非负数。
已经测试过数据中不含0,因为while (!b && a-b < b)也不WA。难道测试数据中有负数?(但这不符合题目说明)
总之我认为这个题要么数据有问题,要么我脑子秀逗了。

另外有博文说:最后的结论是如果两个数相等,或者两数之比大于斐
波拉契数列相邻两项之比的极限((sqrt(5)+1)/2),则先手胜,否则后手胜。
这个比较牛,但不知如何推出来的?

代码

Source Code

Problem: 2348       User: liangrx06
Memory: 236K        Time: 16MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

int main(void)
{
    int a, b;
    while (cin >> a >> b, a || b) {
        if (a < b) swap(a, b);
        bool ans = true;
        while (a%b && a-b < b) {
            b = a-b;
            a = a-b;
            ans = !ans;
        }
        if (ans) printf("Stan wins\n");
        else printf("Ollie wins\n");
    }

    return 0;
}

POJ1082

http://poj.org/problem?id=1082

题意

从1900-1-1到2001-11-4期间任一个日期开始,每次要么向后移动一天,要么向后移动一个月。最后准确到达2001-11-4的为胜者。

思路

从后往前推,对于某一天的胜败由两天决定:后一天和后一个月的同一天(当然有的没有第二个决定因素)
若两者都是必胜态,则这一天必败。若有一天为必败,则这一天必胜。
2001年11月4日到2001年10月4日胜败是交替的,设月+日等于M。发现M为偶数时必胜,为奇数是必败。
2001年10月4日必胜,10月3日由10月4日和11月3号决定,两者都是必胜,所以10月3号必败。同理可判定10月剩余的日子。相邻的两个月的同一天胜败情况相反,但这一天M的奇偶性也相反(月份或日期两者只有一个减一),所以结论成立。
下面考虑特殊情况:
9月只有30天,9月30号由10月1号(必败)和10月30号(必胜)决定,为必胜。与上述结论矛盾,但9月29号为必胜,9月后面的日子依然遵循这样的规律,可见9月30号为一个特殊情况,同样的特殊情况还有11月30号(用同样的方法判断4,6月30号发现遵循结论),对于2月(不管是闰年还是平年)发现也符合。
所以可的结论:除9月30号和11月30号外,M为偶数必胜,为奇数必败。

另外也可以写程序动态规划求解,代码相对要麻烦一些,但思路清晰。这位仁兄就是这样做的:POJ1082动态规划求解

代码

Source Code

Problem: 1082       User: liangrx06
Memory: 240K        Time: 0MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

int main(void)
{
    int t, y, m, d;
    bool ans;

    cin >> t;
    while (t--) {
        scanf("%d%d%d", &y, &m, &d);
        ans = true;
        if ((m+d)%2) ans = false;
        if(d == 30 && (m == 9 || m == 11))
            ans = true;
        if (ans) printf("YES\n");
        else printf("NO\n");
    }

    return 0;
}

POJ2068

http://poj.org/problem?id=2068

题意

有2*n个人坐在一起,分为2组(交叉坐即:1、3、5….是我方,2、4、6….是对方),共有s个石头,每次挨着取石头,石头不能超过当前人取最大的限制,若谁取到最后一个石头,那么就算失败。现在问我方是否能胜利,胜利输出1,失败输出0。

思路

动态规划求解,dp[i][j]表示为第i个人剩下j个石头的胜负情况。初始化所有值为0,然后类似于素数筛法的做法由必败态筛出必胜态。注意思路要清晰,顺序不能搞反。

代码

Source Code

Problem: 2068       User: liangrx06
Memory: 324K        Time: 32MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 10;
const int S = 1<<13;

int n, s;
int m[2*N];
bool dp[2*N][S+1];

int main(void)
{
    while (scanf("%d", &n) != EOF && n) {
        scanf("%d", &s);
        for (int i = 0; i < 2*n; i ++)
            scanf("%d", &m[i]);

        memset(dp, 0, sizeof(dp));
        for (int j = 1; j < s; j ++) {
            for (int i = 0; i < 2*n; i ++) {
                int ii = (i+2*n-1)%(2*n);
                if (dp[i][j] == false) {
                    for (int k = 1; k <= m[ii]; k ++) {
                        if (j + k > s) break;
                        dp[ii][j+k] = true;
                    }
                }
            }
        }
        int ans = dp[0][s] ? 1 : 0;
        printf("%d\n", ans);
    }

    return 0;
}

POJ3688

http://poj.org/problem?id=3688

题意

思路

代码

这里写代码片

POJ1740 变态取石子

http://poj.org/problem?id=1740

题意

取石子游戏,有n堆石子(n<=10),每堆有stone[i]颗石子(<=100),A和B玩游戏,每次操作的人可以任选一堆石子拿出来起码一颗,然后可以选择把剩下的石子移到其他的非空石子堆中,给定局面问是否先手必胜。

思路

传说中楼教主的“男人八题”之一!
这题主要是寻找必败态。
如果只有一堆石子,先手必胜。
如果有两堆石子,并且两堆石子的数量相等,那么先手采取什么样的策略,后手采取一样的策略,先手必败。
如果有三堆石子,那么先手可以在第一步取到只剩两堆相同数量的石子,先手必胜。
如果有四堆石子,由于三堆石子是必胜态,所以无论是先手还是后手都想逼对方取完某一堆石子,只有在四堆石子都为1时,擦能迫使某一方取完一堆石子,通过分析可知,只有当四堆石子可以分成两两相等的两队时,先手必败。
由上我们可以猜测,n 堆石子,当且仅当n为偶数,且n对石子可以分成n/2对两两相等的石子时,先手必败。
证明:
第一条性质:终点是必败态,满足。
第二条性质:必胜点一定有种策略可以进入必败态。
由以上可知,必胜点是当n为奇数或者n为偶数且不能分成n/2对两两相等的石子。
当n为奇数时我们一定可以把其中一堆分到其他堆中,使成n/2对两两相等的石子,第二种情况也是成立的。
第三条性质:必败点只能到达必胜点。显然成立。

代码

Source Code

Problem: 1740       User: liangrx06
Memory: 248K        Time: 0MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 10;

int main(void)
{
    int n;
    int a[N];

    while (cin >> n && n) {
        for (int i = 0; i < n; i ++)
            cin >> a[i];
        int ans = 1;
        if ((n&1) == 0) {
            sort(a, a+n);
            bool flag = true;
            for (int i = 0; i < n; i += 2)
                if (a[i] != a[i+1]) flag = false;
            if (flag) ans = 0;
        }
        cout << ans << endl;
    }

    return 0;
}

你可能感兴趣的:(动态规划,poj,推理,挑战程序设计竞赛,游戏策略)