上海计算机学会 12 月月赛 丙组题解
涉及知识点:数学、字符串、模拟、裴蜀定理、宽度优先搜索、动态规划
比赛链接:https://iai.sh.cn/contest/58
第一题:T1数砖数
标签:数学
题意:给定一种
2
2
2x
2
2
2的瓷砖,样式为
##
.#
用瓷砖,从平面左上角出发,将整个平面铺满。形如:
################
.#.#.#.#.#.#.#.#
################
.#.#.#.#.#.#.#.#
################
.#.#.#.#.#.#.#.#
################
.#.#.#.#.#.#.#.#
给定
n
n
n行
m
m
m列的区域,求这块区域中
#
\#
#的数目。(
1
<
=
n
,
m
<
=
10000
1<=n,m<=10000
1<=n,m<=10000)
题解:考虑从整块区域中删掉
.
.
.的数目。观察发现偶数行、奇数列才有
.
.
.,自己手玩几组数据能够得到
.
.
.的数目是
(
n
/
2
)
∗
(
(
m
+
1
)
/
2
)
(n/2)*((m+1)/2)
(n/2)∗((m+1)/2)。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
cout << n * m - (n / 2) * ((m + 1) / 2);
return 0;
}
第二题:T2移动复位
标签:字符串、模拟
题意:给定一个二维平面上的顶点,然后进行一系列指令操作,指令操作以字符串的形式给出:
R
R
R 表示该点沿
X
X
X 轴坐标正方向移动了一个单位
L
L
L 表示该点沿
X
X
X 轴坐标负方向移动了一个单位
U
U
U 表示该点沿
Y
Y
Y 轴坐标正方向移动了一个单位
D
D
D 表示该点沿
Y
Y
Y 轴坐标负方向移动了一个单位
求这些指令执行完之后,至少再增加多少条指令能让这个点返回起点(原来的位置)。
题解:每次都是上下左右移动一个位置坐标,假设原来起点在原点(
0
,
0
0,0
0,0),最少指令数,即求当前坐标(
x
,
y
x,y
x,y)与原点的曼哈顿距离:
∣
x
∣
+
∣
y
∣
|x|+|y|
∣x∣+∣y∣。
我这边用了
m
a
p
map
map去统计了各个移动方向指令的数量,然后东西和南北方向分别做抵消,求绝对值。
曼哈顿距离:两点在南北⽅向上的距离加上在东西⽅向上的距离。
代码:
#include <bits/stdc++.h>
using namespace std;
map<char, int> m;
int main() {
string s;
cin >> s;
for (int i = 0; i < s.size(); i++) {
m[s[i]]++;
}
cout << abs(m['L'] - m['R']) + abs(m['U'] - m['D']) << endl;
return 0;
}
第三题:T3数轴旅行
标签:裴蜀定理
题意:初始位置在
x
=
0
x=0
x=0,要去
x
=
d
x=d
x=d的位置,给定
n
n
n个整数
a
1
,
a
2
,
a
3
.
.
.
,
a
n
a_1,a_2,a_3...,a_n
a1,a2,a3...,an,表示每次可以往左移动
a
i
a_i
ai个单位或者往右移动
a
i
a_i
ai个单位,求最后能不到达
x
=
d
x=d
x=d的位置。
(
1
<
=
n
<
=
1
0
5
,
1
<
=
a
i
<
=
1
0
9
,
−
1
0
9
<
=
d
<
=
1
0
9
1<=n<=10^5,1<=a_i<=10^9,-10^9<=d<=10^9
1<=n<=105,1<=ai<=109,−109<=d<=109)
题解:经典裴蜀定理题,有兴趣的可以去看看证明,我这边直接说定理和结论。
裴蜀定理:对于
x
x
x,
y
y
y的二元一次不定方程
a
x
+
b
y
=
c
ax+by=c
ax+by=c,其有解的充要条件为
g
c
d
(
a
,
b
)
∣
c
gcd(a,b)|c
gcd(a,b)∣c。
(
g
c
d
(
a
,
b
)
∣
c
gcd(a,b)|c
gcd(a,b)∣c这个数学表示为
c
c
c能被
g
c
d
(
a
,
b
)
gcd(a,b)
gcd(a,b)整除)
由基础定理我们可以得到一个推论:
对于不定方程
x
1
y
1
+
x
2
y
2
+
.
.
.
+
x
n
y
n
=
k
x_1y_1+x_2y_2+...+x_ny_n=k
x1y1+x2y2+...+xnyn=k,
∀
y
i
∈
Z
\forall y_i \in \Z
∀yi∈Z,其有解的充要条件为
gcd
{
x
i
}
∣
k
\gcd\{x_i\}\mid k
gcd{xi}∣k。
回到本题我们可以写出这个式子,
y
i
y_i
yi可以是
−
1
-1
−1和
1
1
1:
a
1
y
1
+
a
2
y
2
+
.
.
.
+
a
n
y
n
=
d
a_1y1+a_2y_2+...+a_ny_n=d
a1y1+a2y2+...+anyn=d
那么问题就转变成了
d
d
d能否被 [ 所有
a
i
a_i
ai的最大公约数 ] 整除,能整除就有解,反之无解。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int main() {
ll n, a, d, g = 0;
cin >> n >> d;
for (int i = 1; i <= n; i++) {
cin >> a;
g = gcd(g, a);
}
if (d % g == 0) cout << "Yes";
else cout << "No";
return 0;
}
第四题:T4迷宫
标签:宽度优先搜索
题意:给定
n
n
nx
m
m
m由
#
\#
#(墙)、
.
.
.(空地)组成的地图,求从左上角到右下角的最少步数,每次只允许上下左右移动一格,不允许走出地图和走到
#
\#
#(墙)上。
题解:BFS 模板题,判断下一个能否走的点,经典三要素:
- 下个点是否是障碍物(宽泛来说是题目中的限制条件)
- 下个点是否走过(避免死循环)
- 下个点是否在地图内
代码:
#include <bits/stdc++.h>
using namespace std;
int n, m;
int dx[4] = {0, 0, -1, 1};
int dy[4] = {-1, 1, 0, 0};
char a[1005][1005];
bool vis[1005][1005];
struct node {
int x, y, step;
};
void bfs() {
queue <node> q;
vis[1][1] = 1;
q.push({1, 1, 0});
while (!q.empty()) {
node u = q.front();
q.pop();
if (u.x == n && u.y == m) {
cout << u.step << endl;
return ;
}
for (int i = 0; i < 4; i++) {
int nx = u.x + dx[i];
int ny = u.y + dy[i];
if (nx < 1 || nx > n || ny < 1 || ny > m) continue;
if (vis[nx][ny] || a[nx][ny] == '#') continue;
vis[nx][ny] = 1;
q.push({nx, ny, u.step + 1});
}
}
cout << "No solution" << endl;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
bfs();
return 0;
}
第五题:T5特定的串
标签:动态规划
题意:给定
01
01
01串,可以修改其中任意一个字符,把
0
0
0变成
1
1
1,把
1
1
1变成
0
0
0,不能删除或者增加
01
01
01字符,求最少修改个数,使得给定序列中不含特定子串
110
110
110。
题解:
贪心
90
90
90 分解法:比较容易想到的一个思路是把
11
11
11变成
10
10
10,或者把所有
0
0
0变成
1
1
1。
这个思路有以下几个反例:
101111101
101111101
101111101(这个只需要把后面的那个
0
0
0改成
1
1
1)
1100111101
1100111101
1100111101(这个可以把第
2
2
2个
1
1
1改成
0
0
0,最后那个
0
0
0改成
1
1
1)
像第二个反例,我们需要思考把两种贪心策略进行结合。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, num = 0; // num: 连续1的个数
// c1: 11110变成10100
// c2: 11011变成11111
int c1 = 0, c2 = 0;
string s;
cin >> n >> s;
for (int i = 0; i < n; i++) {
if (s[i] == '0') {
c2++;
c1 += num / 2;
num = 0;
} else {
num++;
}
}
cout << min(c1, c2) << endl;
return 0;
}
// 9
// 101111101
// 10
// 1100111101
动态规划解法:
d
p
[
i
]
[
0
/
1
]
[
0
/
1
]
dp[i][0/1][0/1]
dp[i][0/1][0/1]:以第
i
i
i位为结尾,使得前
i
i
i个字符中不包含
110
110
110,且第
i
i
i位是
0
0
0或
1
1
1,第
i
−
1
i-1
i−1位是
0
0
0或
1
1
1的最少修改个数。
考虑状态转移:
一、如果
s
[
i
]
s[i]
s[i]修改成
0
0
0
- d p [ i ] [ 0 ] [ 0 ] dp[i][0][0] dp[i][0][0]( s [ i ] = 0 , s [ i − 1 ] = 0 s[i]=0,s[i-1]=0 s[i]=0,s[i−1]=0)可以从 s [ i − 2 ] = 0 / 1 s[i-2]=0/1 s[i−2]=0/1转移过来,即 d p [ i ] [ 0 ] [ 0 ] = m i n ( d p [ i − 1 ] [ 0 ] [ 0 ] , d p [ i − 1 ] [ 0 ] [ 1 ] ) dp[i][0][0] = min(dp[i-1][0][0], dp[i-1][0][1]) dp[i][0][0]=min(dp[i−1][0][0],dp[i−1][0][1])
- d p [ i ] [ 0 ] [ 1 ] dp[i][0][1] dp[i][0][1]( s [ i ] = 0 , s [ i − 1 ] = 1 s[i]=0,s[i-1]=1 s[i]=0,s[i−1]=1)只能从 s [ i − 2 ] = 0 s[i-2]=0 s[i−2]=0转移过来,即 d p [ i ] [ 0 ] [ 0 ] = d p [ i − 1 ] [ 1 ] [ 0 ] dp[i][0][0] = dp[i-1][1][0] dp[i][0][0]=dp[i−1][1][0]
二、如果
s
[
i
]
s[i]
s[i]修改成
1
1
1
那不管是
001
001
001、
101
101
101、
111
111
111
011
011
011都是可以的。所以
d
p
[
i
]
[
1
]
[
0
]
=
m
i
n
(
d
p
[
i
−
1
]
[
0
]
[
0
]
,
d
p
[
i
−
1
]
[
0
]
[
1
]
)
dp[i][1][0] = min(dp[i-1][0][0], dp[i-1][0][1])
dp[i][1][0]=min(dp[i−1][0][0],dp[i−1][0][1])
d
p
[
i
]
[
1
]
[
1
]
=
m
i
n
(
d
p
[
i
−
1
]
[
1
]
[
0
]
,
d
p
[
i
−
1
]
[
1
]
[
1
]
)
dp[i][1][1] = min(dp[i-1][1][0], dp[i-1][1][1])
dp[i][1][1]=min(dp[i−1][1][0],dp[i−1][1][1])
然后再上面转移的过程中要考虑
s
[
i
]
s[i]
s[i]是否修改,所以要把这个修改的代价都带上。
最后从
d
p
[
n
−
1
]
dp[n-1]
dp[n−1]的四种情况里面取最小值就可以了。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 +10;
char s[N];
int dp[N][2][2];
// 以第i位为结尾,使得前i个字符中不包含110,
// 且第i位是0或1,第i-1位是0或1的最少修改个数
int main() {
int n;
cin >> n >> s;
int s0 = s[0] - '0', s1 = s[1] - '0';
dp[1][s1][s0] = 0;
dp[1][s1^1][s0] = dp[1][s1][s0^1] = 1;
dp[1][s1^1][s0^1] = 2;
for (int i = 2; i < n; i++) {
int d = 0;
// s[i]修改成0
if (s[i] != '0') d = 1;
dp[i][0][0] = min(dp[i-1][0][0], dp[i-1][0][1]) + d;
dp[i][0][1] = dp[i-1][1][0] + d;
// s[i]修改成1
d = 0;
if (s[i] != '1') d = 1;
dp[i][1][0] = min(dp[i-1][0][0], dp[i-1][0][1]) + d;
dp[i][1][1] = min(dp[i-1][1][0], dp[i-1][1][1]) + d;
}
cout << min(min(dp[n-1][0][0], dp[n-1][0][1]), min(dp[n-1][1][0], dp[n-1][1][1]));
return 0;
}