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

本文总结了石门实验中学第五届科技节中涉及的四道OI竞赛题目,包括幸运数的计算、拥挤的奶牛问题、矩阵变形的最小操作次数和握手问题的解决方案,每道题目都提供了问题描述、输入输出格式及样例,并分析了解题思路和参考代码。
摘要由CSDN通过智能技术生成

众所周知我校向来发扬有为教育精神,于是在第十三届教育教学开放日的科技节硬是把 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×8 4×6 )。
2. 喜欢的数没有0。

【输入】
输入文件lucky.in共两行。
第一行两个整数 n m n 为小刚最喜欢的数字个数(n30)。 m m10000)为求出来的小刚所喜欢的数 N+ ,且 m )。
第二行 n 个整数,(所有整数100 1
【输出】
输出文件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就会“吵架”。你的任务是计算:在会“吵架”的奶牛当中,体重最大的奶牛的体重是多少?
【输入格式】
第一行, N K 1<=N<=50000 1K<N
接下来有 N 行,第i行是 Wi 0Wi106
【输出格式】
一个整数。
【输入样例】
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 ;
}

第三题 变形

有一个矩阵,有n m 列,每个格子里都有一个数字,数字要么是0要么是1。现在你要用最少的操作去把矩阵的数字全部变成0。每一次操作,你可以做的是:选择一个格子,假设你选择第r行第 c 列的格子(r,c),那么满足 xr yc 的所有格子 (x,y) 里面数字都会自动变反,所谓变反就是如果原来是0那么变成1,如果原来是1那么变成0。
输入格式: trans.in
第一行:两个整数, n m. 1n,m50 .
接下来是 n m列的矩阵。每个格子要么是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
一个整数 N N是偶数,不超过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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值