USACO Section 1.3 Combination Lock 解题报告

题目

题目描述

农夫John的牛从农场逃脱出去了,所以他决定用一个密码锁来把农场的门锁起来,这个密码锁有三个表盘,每个表盘都是环形的,而且上面刻有1~N,现在John设了一个开锁密码,而且这个锁的设计者也有一个开锁密码。这个锁有一个特点,那就是它的容错方式,当我们输入密码时,如果我们的密码与正确的开锁密码相比较,对应的每一个表盘上的数字的距离都相差不超过2时,这个密码便可以打开这个锁。

例如:John设定的开锁密码为(1,2,3),锁的设计者的开锁密码为(4,5,6)。如果我们现在将锁设定为(1,3,5),那么这个密码也可以将这个锁打开,因为我们的密码相对于John的密码每一个表盘上的数字的距离分别为0, 1, 2,没有超过2的距离,所以可以打开。如果我们的密码是(2,4,8),那么同样这个密码对于锁设计者的密码来说,每个表盘上的数字距离分别为2,1,2,所以这个密码也是可以打开的。但是如果我们的密码是(1,5,6),这个密码对于John的密码距离分别为0,3,3,对于设计者的密码距离分别为3,0,0,都存在距离超过2的情况,所以这个密码无法打开这把锁。

现在我们设定一个N,然后输入John的密码与设计者的密码,需要我们计算出有多少种密码是可以打开这把锁的。

数据范围

1 <= N <= 100

样例输入

50
1 2 3
5 6 7

样例输出

249

解题思路

我们用最暴力的方法是可以解决这个问题的,因为这个题目的数据量不是很大,我们可以枚举每一种可能的密码,然后一一判断这个密码是否能够打开这把锁。当然这种方法的时间复杂度是比较高的,对于数据量比较大的时候可能不合适。

解题代码

/*
ID: yinzong2
PROG: combo
LANG: C++11
*/
#define MARK
#include<cstdio>
#include<cstring>
#include<cstdlib>

using namespace std;
const int MAXN = 100+10;

int n;
int farmer[3], master[3];
bool vis[MAXN][MAXN][MAXN];

bool ok(int a, int b) {
    if(abs(a-b) <= 2 || abs(a-b) >= n-2) return true;
    return false;
}

int main() {
#ifdef MARK
    freopen("combo.in", "r", stdin);
    freopen("combo.out", "w", stdout);
#endif // MARK
    while(~scanf("%d", &n)) {
        memset(vis, false, sizeof(vis));
        for(int i = 0; i < 3; i++) {
            scanf("%d", &farmer[i]);
        }
        for(int i = 0; i < 3; i++) {
            scanf("%d", &master[i]);
        }
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                for(int k = 1; k <= n; k++) {
                    if(!vis[i][j][k]) {
                        if((ok(i, farmer[0]) && ok(j, farmer[1]) && ok(k, farmer[2]))
                        || (ok(i, master[0]) && ok(j, master[1]) && ok(k, master[2]))) {
                            ans++;
                            vis[i][j][k] = true;
                        }
                    }
                }
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

解题思路(Type 2)

我们可以利用排列组合的公式来计算,首先我们对John的密码进行分析,这个密码能够产生多少种开锁密码。显然,由John的密码推导出的开锁密码的数量与设计者的密码推导出的开锁密码数量是一样多的,证明我就省略了。然后关键的一步就是去重,因为我们之前统计的John密码推导出来的开锁密码可能会与设计者密码推导出来的开锁密码重复,我们需要将重复的去掉,最终就是我们的结果。

这种解法的关键在于,我们需要利用乘法原理来计算总共有多少,还有就是去重的计算,最后还有我们对于环形表盘的处理,如果计算距离。这种解法可以将时间复杂度降到最低,优化到O(1),是一种非常不错的算法。

解题代码(Type 2)

/*
ID: yinzong2
PROG: combo
LANG: C++11
*/
#define MARK
#include<cstdio>

using namespace std;

int n;
int farmer[3][5], master[3][5];

int makeNumber(int id, int val, int a[3][5]) {
    int cnt = 0;
    int temp;
    for(int i = -2; i <= 2; i++) {
        temp = val + i;
        while(temp <= 0) temp += n;
        while(temp > n) temp -= n;
        bool flag = true;
        for(int j = 0; j < cnt; j++) {
            if(temp == a[id][j]) {
                flag = false;
                break;
            }
        }
        if(flag) {
            a[id][cnt++] = temp;
        }
    }
    return cnt;
}

int testNumber(int id, int val, int a[3][5], int len1) {
    int cnt = 0;
    int len2 = makeNumber(id, val, a);
    for(int i = 0; i < len2; i++) {
        for(int j = 0; j < len1; j++) {
            if(master[id][i] == farmer[id][j]) {
                cnt++;
                break;
            }
        }
    }
    return cnt;
}

int main() {
#ifdef MARK
    freopen("combo.in", "r", stdin);
    freopen("combo.out", "w", stdout);
#endif // MARK
    while(~scanf("%d", &n)) {
        int x;
        int cnt1;
        for(int i = 0; i < 3; i++) {
            scanf("%d", &x);
            cnt1 = makeNumber(i, x, farmer);
        }
        int sum = cnt1*cnt1*cnt1*2;
        int cnt2[3];
        for(int i = 0; i < 3; i++) {
            scanf("%d", &x);
            cnt2[i] = testNumber(i, x, master, cnt1);
        }
        sum -= (cnt2[0] * cnt2[1] * cnt2[2]);
        printf("%d\n", sum);
    }
    return 0;
}
posted @ 2016-09-12 00:08  yinzm  阅读(392)  评论(0编辑  收藏  举报