写在前面
-
这是一篇友好的解题报告
-
为响应勤俭节约的核心价值观,题意省略背景,使用简洁中文
-
为了便于理解,每段思路下都会有代码
-
每题讲解一种个人认为比较简单易懂的方法
-
每题的标题均链接了洛谷(众所周知,洛谷的题解很友好)或者个人认为比较好的题解
-
本篇题解仅供参考,有时间的同学可以通过搜索题目了解每题更多的解法学习
-
由任何问题均可以通过
qq
,询问我 -
由于匆忙赶制,所以可能会有错误或用语不规范请谅解
CF144A Arrival of the General
题意
-
输入数组大小
n
( 2 < = n < = 100 ) (2<=n<=100) (2<=n<=100), -
依次输入
a1,a2,a3......an
( 1 < = a < = 100 ) (1<=a<=100) (1<=a<=100) -
求经过几次交换(只可以前后相邻的两个交换) 保证第一个数最大,最后一个数最小(中间的大小没影响)
思路
首先我们可以通过遍历找到最左边的最大值ma_val
和最右的最小值mi_val
即为ma_pos
和mi_pos
int mi_pos, ma_pos, mi_val, ma_val;
mi_val = a[1], mi_pos = 1;
for (int i = 2; i <= n; i++)
if (a[i] <= mi_val) //由于需要找到最右边的最小值,所以可以等于
mi_val = a[i], mi_pos = i;
ma_val = a[1], ma_pos = 1;
for (int i = 2; i <= n; i++)
if (a[i] > ma_val)
ma_val = a[i], ma_pos = i;
由于只能相邻的交换,所以
我们只需要先将最小值交换到n
,再将最大值交换到1
即为最小次数
由于每次交换后,他的位置变化为1
,所以从x
交换到y
的步数为
y
−
x
y-x
y−x
(
y
>
x
)
(y>x)
(y>x)
要注意当mi_pos<ma_pos
时,两者交换的过程会有交汇,ma_pos
会被向前交换一位
int ans = 0;
ans += (n - mi_pos); //最小值交换到n
if(mi_pos<ma_pos)
ma_pos--;
ans += ma_pos - 1; //最大值交换到1
cout << ans << '\n';
复杂度 O ( n ) O(n) O(n)
CF469A I Wanna Be the Guy
题意
小X
和小Y
最近在玩一个叫做I wanna be the guy
的游戏,这个游戏有n
个关卡。把n
个关卡都通过后,那么就完成游戏了。
可是小X
只能过p
关,小Y只能过q
关。因为他们能力有限,所以他们决定合作。
给出小X和小Y能通过的关卡,他们能完成这个游戏吗?如果可以,输出I become the guy.
,否则,输出Oh, my keyboard!
思路
读入p
个数字和q
个数字,判断1
到n
是否都出现过即可
我们可以开一个标记数组vis[x]
,表示x
这个数是否出现过
我们遍历
p
+
q
p+q
p+q个数字,对于每个数字x
,
v
i
s
[
x
]
+
+
vis[x]++
vis[x]++表示x
这个数字出现过一次
for (int i = 1; i <= p; i++) //遍历p个数
vis[a[i]]++;
for (int i = 1; i <= q; i++) //遍历q个数
vis[b[i]]++;
最后,我们查找vis[1]
到vis[n]
是否有一个为0
即可
for (int i = 1; i <= n; i++)
{
if(vis[i]==0) //只要找到一个,直接退出一个
{
printf("Oh, my keyboard!");
return 0;
}
}
printf("I become the guy.\n"); //没有一个为0,即可通关
return 0;
复杂度 O ( n ) O(n) O(n)
杨辉三角
题意
多组测试,输出杨辉三角形
思路
杨辉三角形规律(即为组合数规律)
每行第一个和最后一个为1
(
c
o
m
b
[
i
]
[
0
]
=
c
o
m
b
[
i
]
[
i
]
=
1
comb[i][0]=comb[i][i]=1
comb[i][0]=comb[i][i]=1)
中间的每个数等于它上方两数之和( c o m b [ i ] [ j ] = c o m b [ i − 1 ] [ j − 1 ] + c o m b [ i − 1 ] [ j ] comb[i][j]=comb[i-1][j-1]+comb[i-1][j] comb[i][j]=comb[i−1][j−1]+comb[i−1][j])
需要注意的是杨辉三角形包含第0
行,为1
个1
我们可以先计算出前30行的组合数,当询问时输出即可(预处理)
for (int i = 0; i < 30; i++)
{
comb[i][0] = comb[i][i] = 1;
for (int j = 1; j < i; j++)
comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j];
}
多组输入,每行最后一个数字后面没有空格
注意输出格式,每个杨辉三角形后面都有一个空行
while (cin >> n)//多组输入
{
for (int i = 0; i < n; i++)//从第0行开始输出
{
for (int j = 0; j < i; j++)
cout << comb[i][j] << ' ';
cout << comb[i][i] << '\n'; //最后一个数直接输出换行
}
cout << '\n';//每个三角形均输出空行
}
return 0;
复杂度 O ( n 2 ) O(n^2) O(n2)
CF208A Dubstep
题意
Vasya
有一句歌词(可能含有空格)
他在第一个单词之前和最后一个单词之后加上若干(可以为0)个"WUB"
再每个单词之间加上若干(至少为1)个"WUB"
,转变成一个新的字符串(无空格)
如"I AM X"
可以变成 "WUBWUBIWUBAMWUBWUBX"
而不能变成 "WUBWUBIAMWUBX"
现在给出转变后的字符串,求出原来的字符串
思路
本题为字符串处理题,不懂字符数组或String
同学可以预习下这一部分
这里讲字符数组的一种解法
字符数组简介(由于简介所以部分用语并不规范,请谅解)
char s[1000]
:定义字符数组
cin>>s
:可以读入字符数组
strlen(s)
:可以得到字符数组的长度
cout<<s
:可以输出字符数组
s[i]
:可以直接访问第i
个字符(第一个下标为0,与数组相同)
注意的是strlen,cout
都需要字符数组的最后一位为\0
我们可以遍历字符数组,
-
遇到
WUB
,将WUB
跳过 -
将非
WUB
的字符写入一个新的字符串Str
遇到下一个
WUB
时,说明新字符串读入结束,将他输出即可
cin >> s; //读入字符串
int k = 0;
int len = strlen(s); //得到字符串的长度
for (int i = 0; i < len;)
{
if(s[i]=='W'&&s[i+1]=='U'&&s[i+2]=='B')//遇到WUB
{
i += 3;//跳过这三个字符
if(k>0)//k>0表示已经记录新的字符串
{
str[k] = 0;//保持字符数组的最后一位为0
cout << str << ' ';
k = 0;//将k归0
}
}
else
str[k++] = s[i], i++; //将新的字符加入str字符串,并跳到下一位
}
if(k>0) //最后一个可能也是字符串,没遇到WUB,没输出
{
str[k] = 0;
cout << str << ' ';
}
复杂度 O ( n ) O(n) O(n)
CF61A Ultra-Fast Mathematician
题意
-
给你两个字符串,每个字符串都代表一个二进制下的数
-
求这两个数异或起来后的值的二进制是多少。
-
保证给的两个字符串一样长,也就是说可能有字符串会有前导0,同时请保证输出的这个二进制数一样长,也就是说不要去掉前导0
-
字符串长度小于100
思路
解释一下异或的意思
异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用1
表示真,0
表示假,则异或的运算法则为:
0
⊕
0
=
0
,
1
⊕
0
=
1
,
0
⊕
1
=
1
,
1
⊕
1
=
0
0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0
0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(相同为0,不同为1),这些法则与加法是相同的,只是不带进位,所以异或常被认作不进位加法
所以,我们只要将两个字符串输入,比较每一位,生成一个新的字符串输出即可
若 a [ i ] = = b [ i ] a[i]==b[i] a[i]==b[i],则 c [ i ] = 0 c[i]=0 c[i]=0,若 a [ i ] ! = b [ i ] a[i]!=b[i] a[i]!=b[i],则 c [ i ] = 1 c[i]=1 c[i]=1
字符数组简介见上题
cin >> a >> b; //读入两个字符串
int len = strlen(a); //得到字符串长度
for (int i = 0; i < len; i++) //遍历两个字符串
if (a[i] == b[i])
c[i] = '0'; //相同新字符串该位为'0'
else
c[i] = '1'; //反之改位为1
c[len] = 0; //最后一位保持0
cout << c;
return 0;
复杂度 O ( n ) O(n) O(n)
F - To and Fro
题意:找出字符串的存储规律,输出就行了:
思路
我们可以发现奇数行是正序的,偶数行是倒序的
我们可以n
个,n
个遍历数组,将字符加入二维字符数组,还原二维数组
cin >> s;
int len = strlen(s); //得到长度
int m = len / n; //m为二维数组行数
int k = 0; //k为查询的字符数组的第几位
for (int i = 1; i <= m; i++)//矩阵的第i行
{
if (i % 2 == 1)//奇数行
{
for (int j = 1; j <= n; j++)//正序填入
a[i][j] = s[k++];
}
else
{
for (int j = n; j >= 1; j--)//倒序填入
a[i][j] = s[k++];
}
}
按题意,竖着输出字符即可
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cout << a[j][i];
cout << '\n';
复杂度$O(n) $
G - 单词数
题意
多组输入
每组一行,每行有多个单词,统计每行不同单词的个数
以#
结束
思路
此题对于刚接触acm
的同学较难
这里介绍一种比较简单的方法
我们可以通过getchar()
连续读入字符
-
当读入小写字母时,将他加入新的字符串
str
(string 可以直接通过加法连接字符)- (不懂string可以点击标题链接,学习字符数组的解法或者学习string)
-
当读入非小写字母时
- 说明一个字符串输入完毕,将
str
加入map
(听说yx
学姐已经给你们讲过map
了) - 若读入
\n
换行符,说明一行已经读入完毕,输出map.size()
- 说明一个字符串输入完毕,将
map<string, bool> Map;
string str;
char ch;
while ((ch = getchar()) != '#') {
if (ch >= 'a' && ch <= 'z') {
str += ch;
} else {
if (str.size() > 0) {
Map[str] = true;
str = "";
}
if (ch == '\n') {
cout << Map.size() << endl;
Map.clear();
str = "";
}
}
}
复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
H - 不要62
题意
- 杭州认为一个数字中包含
4
或者62
是不吉利的 - 询问从
n
到m
包含几个吉利的数字
思路
需要判断一个数字是否位吉利数字,我们需要遍历她的每一位
对于x
,我们通过x%10
可以得到x
的最后一位,然后x/10
将最后一位消除
如此我们就得到一个数字的最后一位,重复以上过程值得x==0
,我们就能得到一个数字的每一位
int a[7], k = 0;
while (x) {
a[k++] = x % 10;
x /= 10;
}
显然,我们的得到的数字是倒序的,所以不要62
就变成了不要26
我们遍历即可判断一个数是否吉利
bool check(int x){
int a[7], k = 0;
while(x){
a[k++] = x % 10;
x /= 10;
}
a[k] = 0;
for(int i=0;i<k;i++)
if(a[i]==4)
return false;
else if(a[i]==2&&a[i+1]==6)
return false;
return true;
}
对于每组询问都遍历n
到m
是不现实的,想想都会TLE
所以我们可以暴力预处理好每一位数是否吉利
然后用前缀和优化,即用sum[i]
表示0
到i
有多少个吉利的数
那么n
到m
有多少个吉利数即为
s
u
m
[
m
]
−
s
u
m
[
n
−
1
]
sum[m]-sum[n-1]
sum[m]−sum[n−1]
for (int i = 1; i <= 1000000; i++)
if (check(i))
sum[i] = sum[i - 1] + 1;
else
sum[i] = sum[i - 1];
int n, m;
while (cin >> n >> m) {
if (n == 0 && m == 0)
break;
cout << sum[m] - sum[n - 1] << '\n';
}
复杂度 O ( n ) O(n) O(n)
I - 超级楼梯
题意
有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?
思路
我们可以倒过来想,对于第i
台阶,只有可能由i-1
阶或i-2
阶迈上
(
i
>
=
3
)
(i>=3)
(i>=3)
所以迈上第i
阶的方案为,迈上第i-1
阶的方案加上迈上第i-2
阶的方案
显然第1,2
阶方案均为1
,细心的同学一定已经发现这就是斐波那契数列了
我们只需要预处理1
到40
的方案数,返回询问的值即可
int fib[45] = { 0, 1, 1 };
for (int i = 3; i <= 40; i++)//计算fib数
fib[i] = fib[i - 1] + fib[i - 2];
int t, n;
cin >> t;
while (t--) {
cin >> n;
cout << fib[n] << '\n';
}
复杂度 O ( n ) O(n) O(n)
J - Fibsieve`s Fantabulous Birthday
题意
-
给出一个矩阵,该矩阵以左下角为中心绕圈填数
-
询问数字
n
所在的坐标
思路
我们观察到
- 对角线上的数为 n ∗ ( n − 1 ) + 1 n*(n-1)+1 n∗(n−1)+1
- 每圈上的数刚好介于两个平方数中间,即只要求出平方根,即可知道层数
上面两个规律是为什么? 可以思考下
-
以
x=1
为起点,对于奇数层上的数递减,偶数层数上的数递增 -
分类讨论,以对角线上的点为中心点,计算
n
与对角的差距即可 -
注意特殊处理平方数和
long long
cin >> n;
long long base = sqrt(n * 1.0); //平方根
if (base * base == n) { //平方数
if (base & 1)
cout << "1 " << base << '\n';
else
cout << base << ' ' << 1 << '\n';
continue;
}
long long limit = base * (base + 1) + 1;//对角线上的数
if (base & 1) { //奇数层
if (n < limit)
cout << base + 1 - (limit - n) << ' ' << base + 1 << '\n';
else
cout << base + 1 << ' ' << base + 1 - (n - limit) << '\n';
} else { //偶数层
if (n < limit)
cout << base + 1 << ' ' << base + 1 - (limit - n) << '\n';
else
cout << base + 1 - (n - limit) << ' ' << base + 1 << '\n';
}
K - Train Problem I
题意
给出火车站的火车入站顺序和出站顺序
火车站只有一个门,请问是否可能?
思路
模拟栈
因为火车站只有一个门,所以和栈的结构是一样的
后进站的火车必须先出,在里面的火车出不来
所以我们发现这和栈是一样的
栈简介:栈中元素先进后出
st.push(val)
:将val
加入栈
st.top()
:返回栈顶元素的值
st.pop()
:弹出栈顶的元素
st.empty()
:判断栈是否为空
我们知道入栈顺序和出栈顺序
我们可以模拟这个过程
- 先按序将火车压进栈,并比较当前栈顶与出栈顺序
- 若当前栈顶元素为下一个出栈的元素,那么他必须马上出栈
- 因为当下一个元素进栈,第一个出栈的元素只能栈顶元素,产生矛盾
- 当栈顶元素相同时,要将能出栈的都出栈,而不是只比较一个
- 若最后栈为空,表示成功,否则失败
- 过程用数组记录,操作的过程,如
1
表示入栈,2
表示出栈
scanf("%s%s", s1, s2); //入栈和出栈顺序
int k = 0, j = 0; //j为下一个出栈元素的下标
for (int i = 0; i < n; i++) {
st.push(s1[i]); //入栈
res[k++] = 1; //记录入栈
while (!st.empty() && st.top() == s2[j]) {//若栈顶元素需要出栈
st.pop();
res[k++] = 2; //记录出栈
j++;
}
}
if (!st.empty()) {
cout << "No.\n";
while (!st.empty())
st.pop();
} else {
cout << "Yes.\n";
for (int i = 0; i < k; i++)
if (res[i] == 1)
cout << "in\n";
else
cout << "out\n";
}
cout << "FINISH\n";
复杂度: O ( n ) O(n) O(n)
L - ACboy needs your help again!
题意
给出操作方式,FIFO
(先进先出)或FILO
(先进后出)
请输出out
的数
思路
先进先出:队列
先进后出:栈
队列:队列中元素先进先出
q.push(val)
:将val
加入队列
q.front()
:返回队首元素的值
q.pop()
:弹出队首的元素
模拟,他的操作即可
FIFO
(队列模拟)
queue<int> q;
while (n--) {
cin >> opt;
if (opt[0] == 'I') { //in
cin >> x;
q.push(x); //入队
} else { //out
if (!q.empty()) { //不为空
cout << q.front() << '\n';
q.pop();
} else
cout << "None\n";
}
}
FILO
(栈模拟)
stack<int> st;
while (n--) {
cin >> opt;
if (opt[0] == 'I') {
cin >> x;
st.push(x);
} else {
if (!st.empty()) {
cout << st.top() << '\n';
st.pop();
} else
cout << "None\n";
}
}
复杂度$O(n) $
M - Ugly Numbers
题意
题目要求我们输入一个数字n,输出第n个丑数,丑数的要求是为其质数因子仅有2、3、5。
思路
这里提供一个用set
暴力的思路
题目链接了一个更精妙的思路
由于质因子只有2,3,5
,所以对于每个数x
,2*x
,3*x
,5*x
均为丑数
由于我们要找前1500
小的,我们想到一个STL:set
,带有去重和排序功能
set
简介:set
可以对加入的数据去重和排序
s.insert(val)
:插入值为val
的元素
s.clear()
:清空set
s.size()
:set
的大小
set<T>::iterator iter
:迭代器iter
set
的遍历需要使用迭代器iter
set<int>::iterator iter;
for (iter = s.begin(); iter !s.end(); iter++)
cout << *iter << ' ';
-
我们每次只要找到最小的值
x
,将2*x
,3*x
,5*x
加入, -
再次找到剩余数中的最小值重复以上步骤即可
原理相当于做个完全背包(qwq),当然显然也是对的
set<long long> Set;
Set.insert(1);
set<long long>::iterator iter = Set.begin();
for (int i = 1; i <= 1500; i++, iter++) {
res[i] = *iter;
Set.insert(res[i] * 2);
Set.insert(res[i] * 3);
Set.insert(res[i] * 5);
}
在提供一种优先队列的写法,需要去重
priority_queue<long long, vector<long long>, greater<long long> > q;
//优先队列,以小的为优先
q.push(1);
for (int i = 1; i <= 1500; i++) {
while (q.top() == res[i - 1])//去重
q.pop();
res[i] = q.top();
q.pop();
q.push(res[i] * 2);
q.push(res[i] * 3);
q.push(res[i] * 5);
}
复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
::iterator iter:迭代器
iter`
set
的遍历需要使用迭代器iter
set<int>::iterator iter;
for (iter = s.begin(); iter !s.end(); iter++)
cout << *iter << ' ';
-
我们每次只要找到最小的值
x
,将2*x
,3*x
,5*x
加入, -
再次找到剩余数中的最小值重复以上步骤即可
原理相当于做个完全背包(qwq),当然显然也是对的
set<long long> Set;
Set.insert(1);
set<long long>::iterator iter = Set.begin();
for (int i = 1; i <= 1500; i++, iter++) {
res[i] = *iter;
Set.insert(res[i] * 2);
Set.insert(res[i] * 3);
Set.insert(res[i] * 5);
}
在提供一种优先队列的写法,需要去重
priority_queue<long long, vector<long long>, greater<long long> > q;
//优先队列,以小的为优先
q.push(1);
for (int i = 1; i <= 1500; i++) {
while (q.top() == res[i - 1])//去重
q.pop();
res[i] = q.top();
q.pop();
q.push(res[i] * 2);
q.push(res[i] * 3);
q.push(res[i] * 5);
}
复杂度 O ( n l o g n ) O(nlogn) O(nlogn)