众所周知我校向来发扬有为教育精神,于是在第十三届教育教学开放日的科技节硬是把 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 为小刚最喜欢的数字个数(n≤30 )。 m (m≤10000 )为求出来的小刚所喜欢的数 ∈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满足: n≤10,m≤100
数据11~30满足: n≤30,m≤100
数据31~60满足: n≤10,m≤10000
数据61~100满足: n≤30,m≤10000
这题本来想着直接分解质因数再判断的,但是由于给出来的原始喜欢数比较乱,所以比较难写。后来改了一下,其实可以直接用类似筛法的方法直接搞就可以了,时间复杂度为 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
【题目描述】
有 N 奶牛,从左往右排成一行,编号是1至N ,第 i 头奶牛的体重是Wi 。假设奶牛i和奶牛j的体重相同而且 j>i ,如果满足 j−i≤K ,那么奶牛 i 和奶牛j 就会“吵架”。你的任务是计算:在会“吵架”的奶牛当中,体重最大的奶牛的体重是多少?
【输入格式】
第一行, N 和K 。 1<=N<=50000 , 1≤K<N 。
接下来有 N 行,第i 行是 Wi 。 0≤Wi≤106 。
【输出格式】
一个整数。
【输入样例】
6
3
7
3
4
2
3
4
【输出样例】
4
【样例解释】
第3头奶牛重量是4,第6头奶牛的重量也是4,而且 6−3≤K ,所以第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) ,那么满足 x≤r 且 y≤c 的所有格子 (x,y) 里面数字都会自动变反,所谓变反就是如果原来是0那么变成1,如果原来是1那么变成0。
输入格式: trans.in
第一行:两个整数, n 和m . 1≤n,m≤50 .
接下来是 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 条的时候,考虑如下的情况:确定了一条线,剩下
i−1 条;因为其它连线不能与这条已确定的连线相交叉,于是两边就被分成了两个独立的子问题,有 i 种情况:左边取 0 条,右边取i−1 条;左边取 1 条,右边取 i−2 条……左边取 i−1 条,右边取 0 条。归纳一下,有
fi=∑j=0i−1fj×fi−j−1不难看出这其实就是一个 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。