石门实验中学第五届科技节(思维艺术)总结

来源:互联网 发布:防螨虫床上用品 知乎 编辑:程序博客网 时间:2024/04/29 02:48

众所周知我校向来发扬有为教育精神,于是在第十三届教育教学开放日的科技节硬是把 OI 拉出来凑个数成了一个项目。lws 煞费苦心不知道从哪里搞来了一套题目,结果 40 分钟要我们做 4 道题,不开心。
result = 1 + 80 + 41 + 0 = 122, rank = 6.

第一题 幸运数(lucky.pas/c/cpp)

【背景描述】
小刚背完了单词,上课铃又响了……

【问题描述】
第二节是数学课。小刚最喜欢一些数,也喜欢这些数的乘积(包括自己乘自己,如 6×6)。比如他最喜欢3、5、10,则他在不大于一百的正整数中喜欢3(3)、5(5)、9(3×3)、10(10)、15(3×5)、25(5×5)、27(3×3×3)、30(3×10)、45(3×3×5)、50(5×10)、75(3×5×5)、81(3×3×3×3)、90(3×3×10)、100(10×10)14个。
请你帮他输出他在一定范围内喜欢的数。
注意:
1. 如果有两个相同的乘积,则算一个。如喜欢3、8、4、6,则喜欢24(3×84×6)。
2. 喜欢的数没有0。

【输入】
输入文件lucky.in共两行。
第一行两个整数nmn为小刚最喜欢的数字个数(n30)。mm10000)为求出来的小刚所喜欢的数N+,且m)。
第二行n个整数,(所有整数1001
【输出】
输出文件lucky.out共两行。
第一行是一个整数k,表示小刚所喜欢的数的个数。
第二行输出k个小于等于m的所有小刚喜欢的数。
(如果k=0则只输出No answer!
他在不大于十的正整数中喜欢3(3)、5(5)、9(3*3)、10(10)共4个。
【输入输出样例1】
lucky.in
3 100
3 5 10
lucky.out
14
3 5 9 10 15 25 27 30 45 50 75 81 90 100
【输入输出样例1解释】
他在不大于一百的正整数中喜欢3(3)、5(5)、9(3×3)、10(10)、15(3×5)、25(5×5)、27(3×3×3)、30(3×10)、45(3×3×5)、50(5×10)、75(3×5×5)、81(3×3×3×3)、90(3×3×10)、100(10×10)14个。
【输入输出样例2】
lucky.in
3 1
3 5 10
lucky.out
No answer!

【限制】
数据1~10满足:n10,m100
数据11~30满足:n30,m100
数据31~60满足:n10,m10000
数据61~100满足:n30,m10000


这题本来想着直接分解质因数再判断的,但是由于给出来的原始喜欢数比较乱,所以比较难写。后来改了一下,其实可以直接用类似筛法的方法直接搞就可以了,时间复杂度为 O(nm)

参考代码:

#include <algorithm>#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>using namespace std;const int maxn = 30 + 10;const int maxm = 10000 + 10;int n, m;int a[maxn];bool f[maxm];int main(void) {    freopen("lucky.in", "r", stdin);    freopen("lucky.out", "w", stdout);    scanf("%d%d", &n, &m);    for (int i = 0; i < n; i++) {        scanf("%d", &a[i]);        f[a[i]] = true;    }    sort(a, a + n);    int ans = 0;    for (int i = 1; i <= m; i++) {        ans += f[i];        for (int j = 0; j < n && i * a[j] <= m; j++)            f[i * a[j]] |= f[i];    }    if (ans) {        printf("%d\n", ans);        for (int i = 1; i <= m; i++) if (f[i]) printf("%d ", i);    } else puts("No answer!");    return 0;}

第二题 拥挤的奶牛 proximity

【题目描述】
奶牛,从左往右排成一行,编号是1至N,第i头奶牛的体重是Wi。假设奶牛i和奶牛j的体重相同而且j>i,如果满足jiK,那么奶牛i和奶牛j就会“吵架”。你的任务是计算:在会“吵架”的奶牛当中,体重最大的奶牛的体重是多少?
【输入格式】
第一行,NK1<=N<=500001K<N
接下来有N行,第i行是Wi0Wi106
【输出格式】
一个整数。
【输入样例】
6
3
7
3
4
2
3
4
【输出样例】
4
【样例解释】
第3头奶牛重量是4,第6头奶牛的重量也是4,而且63K,所以第3头奶牛与第6头奶牛会吵架,体重是4。虽然第2头奶牛和第5头奶牛业会吵架但是体重小些。第1头奶牛体重最大,但是没人和它吵架。


这题的方法也比较多。考虑到奶牛的重量范围比较小,完全可以用数组计数标记上一头同重量的奶牛出现的位置,直接相减并判断即可。

参考代码:

#include <algorithm>#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>using namespace std;const int maxn = 50000 + 10;const int maxm = 1e6 + 10;int n, k;int w[maxn];int f[maxm];int main(void) {    freopen("proximity.in", "r", stdin);    freopen("proximity.out", "w", stdout);    scanf("%d%d", &n, &k);    int ans = 0;    for (int i = 1; i <= n; i++) {        int w;        scanf("%d", &w);        if (f[w] && i - f[w] <= k) ans = max(ans, w);        f[w] = i;    }    printf("%d\n", ans);    return 0;}

还有一种思想:可以维护一个长度为 k 的滑动窗口,用数组计数统计同重量的奶牛的数量,如果在当前区间内有不少于两头同重量的奶牛,那么它们吵架。

lyy 的代码:

#include<iostream>#include<cstdio>using namespace std;const int maxn = 50010 ;const int maxk = 1000010 ;int n , k , ans , cow[maxn] , sum[maxk] ;int main(){    freopen ( "proximity.in" , "r" , stdin ) ;    freopen ( "proximity.out" , "w" , stdout ) ;    scanf ( "%d%d" , &n , &k ) ;    for (int i=1 ; i<=n ; i++)    {        scanf ( "%d" , &cow[i] ) ;        if ( i-1<=k ) sum[cow[i]] ++ ;        if ( sum[cow[i]]>=2 ) ans=max(ans,cow[i]) ;    }    for (int i=k+2 ; i<=n ; i++)     {        sum[cow[i-k-1]] -- ;        sum[cow[i]] ++ ;        if ( sum[cow[i]]>=2 ) ans=max(ans,cow[i]) ;    }    printf ( "%d" , ans ) ;    return 0 ;}

第三题 变形

有一个矩阵,有nm列,每个格子里都有一个数字,数字要么是0要么是1。现在你要用最少的操作去把矩阵的数字全部变成0。每一次操作,你可以做的是:选择一个格子,假设你选择第r行第c列的格子(r,c),那么满足xryc的所有格子(x,y)里面数字都会自动变反,所谓变反就是如果原来是0那么变成1,如果原来是1那么变成0。
输入格式: trans.in
第一行:两个整数,nm. 1n,m50.
接下来是nm列的矩阵。每个格子要么是0要么是1.
输出格式:trans.out
至少需要多少次操作,才能使得矩阵所有格子的数字都是0. 输入数据保证一定有解。
输入样例1:
2 4
0000
0000
输出样例1:
0 (矩阵数字已经全部都是0了,不需要任何操作)
输入样例2:
2 5
11111
11111
输出样例2:
1 (只需要一次操作,你选定格子(2,5),则选定右下角的格子,那么矩阵的所有格子的数字都是取反,全部变成0。)
输入样例3:
2 2
01
01
输入样例3:
2
输入样例4:
2 2
00
01
输入样例4:
4


这题其实就是一个贪心,由于每次选定一个点修改会影响它的左上方,为了避免重复修改造成的浪费,只要从右下往左上不断找1并修改就可以了。时间复杂度为 O(n4)

参考代码:

#include <algorithm>#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>using namespace std;const int maxn = 50 + 10;int n, m;char a[maxn][maxn];int lastn, lastm;bool check() {    int ln = -1, lm = -1;    for (int i = 0; i <= n; i++)        for (int j = 0; j <= m; j++)            if (a[i][j] == '1') ln = i, lm = j;    lastn = ln; lastm = lm;    return ln != -1 && lm != -1;}void trans() {    for (int i = 0; i <= lastn; i++)        for (int j = 0; j <= lastm; j++)            a[i][j] = '1' - a[i][j] + '0';}void debug_output() {    printf("%d %d\n", lastn, lastm);    for (int i = 0; i < n; i++) puts(a[i]); putchar('\n');}int main(void) {    freopen("trans.in", "r", stdin);    freopen("trans.out", "w", stdout);    scanf("%d%d", &n, &m);    for (int i = 0; i < n; i++) scanf("%s", a[i]);    lastn = n - 1; lastm = m - 1;    int ans;    for (ans = 0; check(); ans++) trans()/*, debug_output()*/;    printf("%d\n", ans);    return 0;}

第四题 握手

N个人(N是偶数,不超过50),坐在一个圆桌旁开会,会后,他们决定握手,每个人只能挑另一个人握手, 假设对于握手的两个人,我们画都一条直线,我们要求所有的直线不能有交点。有多少种不同握手方法?
输入格式:shake.in
一个整数NN是偶数,不超过50。
输出格式:shake.out
一个整数,不同的合法握手方案。
输入样例1:
2
输出样例2:
1 (只有2个人,所以他们握手肯定是合法的。)
输入样例2:
4
输出样例2:
2
样例解释:如下有3个图,前2个图的握手是合法的,第3种方案是非法的。


这个题考试的时候我想不出来(囧),后来还是 lyy 给我讲的。

可以把题目抽象成 n 个点之间的不交叉连 n÷2 条线方案计数。显然 0 条和 1 条都只有一种方案,而当连 i 条的时候,考虑如下的情况:

确定了一条线,剩下 i1 条;因为其它连线不能与这条已确定的连线相交叉,于是两边就被分成了两个独立的子问题,有 i 种情况:左边取 0 条,右边取 i1 条;左边取 1 条,右边取 i2 条……左边取 i1 条,右边取 0 条。

归纳一下,有

fi=j=0i1fj×fij1
不难看出这其实就是一个 Catalan 数。

参考代码:

#include <algorithm>#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>using namespace std;const int maxn = 50;int n;unsigned long long dp[maxn];int main(void) {    freopen("shake.in", "r", stdin);    freopen("shake.out", "w", stdout);    scanf("%d", &n); n >>= 1;    dp[0] = dp[1] = 1;    for (int i = 2; i <= n; i++)        for (int j = 0; j < i; j++)            dp[i] += dp[j] * dp[i - j - 1];    printf("%I64d\n", dp[n]);    return 0;}


PS:lws 还讲了一种做法是基于点的:对于某个固定点,枚举与它连线的点,同样分成两个子问题。不过我没编:-P。

0 0
原创粉丝点击