上海计算机学会11月月赛 丙组题解
本次比赛涉及算法:数学、排列组合、高精度除法、思维、排序。本次丙组的比赛主要涉及数学比较多,还有一道高精度除法简化版。
比赛链接:https://iai.sh.cn/contest/57
第一题:T1刷题
标签:数学
题意:给定题目总量
n
n
n和每天完成的题目数量
m
m
m,求需要的天数。
题解:简单数学,不能整除的话多一天。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
if (n % m) cout << n / m + 1;
else cout << n / m;
return 0;
}
第二题:T2染色
标签:数学、组合数学、乘法原理
题意:给定
n
n
n个点和每个点能染色的范围:
1
1
1到
a
i
a_i
ai之间的整数,求最终染色方案,答案对
1
0
9
+
7
10^9+7
109+7取模。
题解:比如样例中的
4
、
7
、
2
4、7、2
4、7、2,染色方案:
2
∗
(
4
−
1
)
∗
(
7
−
2
)
=
30
2*(4-1)*(7-2)=30
2∗(4−1)∗(7−2)=30。我们先给序列从小到大排下序,排完序之后,比如样例:
2
、
4
、
7
2、4、7
2、4、7,第一个点有
2
2
2种染色选择(
1
、
2
1、2
1、2);第二个点正常可以有
4
4
4种染色方案,但是第一个点占用了一种颜色,染色选择减一;同理,对于第三个点来说,被前两个点占用了两种颜色,染色选择减二,依此类推,通过乘法原理 最终就能求解了。过程中,记得通过同余定理对模数取模。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll a[300005], ans = 1;
int main() {
ll n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
ans = ans * (a[i] - i + 1) % mod;
}
cout << ans << endl;
return 0;
}
第三题:T3数根(二)
标签:高精度除法
题意:给定一个正整数
n
n
n,定义一个数字的根为它的十进制数字之和,例如
1234
1234
1234的数根为
1
+
2
+
3
+
4
1+2+3+4
1+2+3+4,请判定
n
n
n的数根能否整除
n
n
n。(
1
<
=
n
<
=
1
0
100000
1<=n<=10^{100000}
1<=n<=10100000)
题解:高精度数以字符串形式输入,先求数根,最坏情况
99999
99999
99999位都是
9
9
9,显然数根是个低精度数。所以题目就转换成高精度数除于低精度数题目,最终看下取模的余数是否为
0
0
0,判断下能否整除即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int m = 0, k = 0;
string s;
cin >> s;
for (int i = 0; i < s.size(); i++) {
m += (s[i] - '0');
}
for (int i = 0; i < s.size(); i++) {
k = 10 * k + (s[i] - '0');
k %= m;
}
if (k > 0) cout << "No";
else cout << "Yes";
return 0;
}
第四题:T4攻击的车
标签:数学、思维、排序
题意:给定
r
r
r行
c
c
c列的国际棋盘。棋盘上有
n
n
n只车,第
i
i
i只车在第
x
i
x_i
xi行,第
y
i
y_i
yi列,每只车能攻击同一行和同一列(包括自身),求这些车能够攻击的方格总数。
题解:洛谷 P3913 车的攻击(原题),数据比较大,直接模拟格子肯定不可行。我们先把车的坐标输入,分别排序,看看去重后 实际被攻击到的行数和列数有多少。如果正着直接用 实际被攻击的行数和实际被攻击到的列数乘积,发现会有重叠的部分,所以可以考虑换个思路,用总的格子数减去未被攻击的行数和列数的乘积(即不被攻击的格子数)。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x[1000005], y[1000005];
int main() {
ll r, c, n;
cin >> r >> c >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
}
sort(x + 1, x + 1 + n);
sort(y + 1, y + 1 + n);
ll c1 = 1, c2 = 1;
for (int i = 2; i <= n; i++) {
if (x[i] != x[i - 1]) c1++;
if (y[i] != y[i - 1]) c2++;
}
// 总的格子数减去未被攻击的格子数
cout << r * c - (r - c1) * (c - c2);
return 0;
}
第五题:T5推箱子
标签:排序、数学、思维
题意:给定
t
t
t组数据,每组数据给定长度为
n
n
n的字符串,
@
@
@表示箱子,
_
\_
_表示空格,求把箱子都推到一起(即两两箱子之间没有空格),最少移动次数。
题解:我们稍微把题意变下,给定
n
n
n个点在
x
x
x轴上的的坐标,求把这些点移到同一个位置的最少移动次数。
举个例子(坐标):
3
5
8
10
3 \ 5 \ 8 \ 10
3 5 8 10
如果把这些点都移到
2
2
2的坐标,移动次数为:
1
+
3
+
6
+
8
=
18
1+3+6+8=18
1+3+6+8=18
如果把这些点都移到
3
3
3的坐标,移动次数为:
0
+
2
+
5
+
7
=
14
0+2+5+7=14
0+2+5+7=14
如果把这些点都移到
5
5
5的坐标,移动次数为:
2
+
0
+
3
+
5
=
10
2+0+3+5=10
2+0+3+5=10
如果把这些点都移到
6
6
6的坐标,移动次数为:
3
+
1
+
2
+
4
=
10
3+1+2+4=10
3+1+2+4=10
如果把这些点都移到
8
8
8的坐标,移动次数为:
5
+
3
+
0
+
2
=
10
5+3+0+2=10
5+3+0+2=10
会发现把点移到这些点中最中间那个点,移动次数最少,推箱子问题同理,只不过得去考虑每个箱子会占一个位置,在中间位置(
k
k
k)左边的第一个箱子推到
k
−
1
k-1
k−1的位置,其他的同理,即对于第
i
i
i 个箱子来说,需要移动的距离为 a[k] - a[i] - (k-i)(i<k);在中间位置右边的第
i
i
i个箱子,同理,需要移动的距离为 a[i] - a[k] - (i-k)(i>k)。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
string s;
ll a[1000005];
int main() {
ll t, n;
cin >> t;
while (t--) {
cin >> n >> s;
ll c = 0, ans = 0;
for (int i = 0; i < n; i++) {
if (s[i] == '@') a[++c] = i;
}
sort(a + 1, a + 1 + c);
ll k = (c + 1) / 2;
for (int i = 1; i <= c; i++) {
if (i < k) ans += (a[k] - a[i] - (k - i));
else ans += (a[i] - a[k] - (i - k));
}
cout << ans << endl;
}
return 0;
}