备战蓝桥杯(非专业C++版)
洛谷:P1518\1781\1217\3406\3375
1-5:1518\1781\1217\3406\3375
1.P1518 [USACO2.4] 两只塔姆沃斯牛 The Tamworth Two
难度:
问题描述:
在一个10x10的方格牧场中:
-
农夫(F)和牛(C)一开始位于不同位置。
-
每秒钟,两者都尝试朝着他们面朝的方向移动。
-
如果前面是围栏(
*
)或出界,就原地转向(右转90度),不移动。
-
-
每秒两者会 同时 进行一次动作。
-
问:他们多久能碰面?若永远碰不到,输出0。
P1518 [USACO2.4] 两只塔姆沃斯牛 The Tamworth Two - 洛谷
分析与思路:
这题是纯模拟题,模拟两者的运动状态,包括坐标位置(x,y)和面朝方向(d=0,1,2,3)。所有的运动状态(每次运动的可显示的结果)一共有,10*10*4*10*10*4种,我们要记录访问过的所有状态(f位置+f方向+c位置+c方向)。无法相遇说明进入了一种死循环的状态,所以重复即无法相遇。
-
农夫 F 和奶牛 C 都从地图上的某个位置出发,初始都面朝“上”。
-
每秒它们都:
-
看看正前方是否为墙(
'*'
); -
如果是墙,就顺时针转向;
-
如果不是墙,就向前走一步;
-
-
你要模拟每一秒的动作,直到它们两个“站在同一个格子”。
-
如果永远无法相遇,输出
0
。
代码部分:
#include <iostream>
#include <cstring>
using namespace std;
struct Entity {
int x, y, dir; // 位置 + 面朝方向
};
char m[12][12]; // 地图
bool visited[11][11][4][11][11][4]; // 状态记录
int dx[4] = {-1, 0, 1, 0}; // 上右下左
int dy[4] = {0, 1, 0, -1};
// 尝试移动实体
void move(Entity &e) {
int nx = e.x + dx[e.dir];
int ny = e.y + dy[e.dir];
if (m[nx][ny] != '*') {
e.x = nx;
e.y = ny;
} else {
e.dir = (e.dir + 1) % 4; // 顺时针转向
}
}
int main() {
Entity farmer, cow;
// 初始化地图边界为障碍
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 12; j++) {
m[i][j] = '*';
}
}
// 读取地图(1~10)
for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 10; j++) {
cin >> m[i][j];
if (m[i][j] == 'F') {
farmer = {i, j, 0}; // 初始朝上
}
if (m[i][j] == 'C') {
cow = {i, j, 0}; // 初始朝上
}
}
}
int time = 0;
while (true) {
if (visited[farmer.x][farmer.y][farmer.dir][cow.x][cow.y][cow.dir]) {
cout << 0 << endl;
return 0; // 已经访问过这个状态,说明进入循环
}
visited[farmer.x][farmer.y][farmer.dir][cow.x][cow.y][cow.dir] = true;
if (farmer.x == cow.x && farmer.y == cow.y) {
cout << time << endl;
return 0;
}
move(farmer);
move(cow);
time++;
}
return 0;
}
2.P1781宇宙总统
难度:
问题描述:
有 n 位候选人,每人都有一个 竞选编号(可以非常长,有上千位数字)。
总统是编号字典序最大的那一位。
编号长度最长为 100(可能超过 long long),所以不能直接转数字比较,要用字符串比较。
P1781 宇宙总统 - 洛谷
分析与思路:
存储长数字用string,比较大小,可以利用位数和大小相结合的方法。
代码部分:
//P1781
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct no1{
string x;//票数
int num;//几号
int lenx;//票数的位数
};
bool cmp(no1 a,no1 b){//排序规则
return (a.lenx>b.lenx||(a.lenx==b.lenx && a.x>b.x));
}
int main(){
int n;
cin>>n;
no1 s[n+1];
for(int i=1;i<=n;i++){
cin>>s[i].x;
s[i].num=i;
s[i].lenx=s[i].x.size();
}
sort(s+1,s+1+n,cmp);
cout<<s[1].num<<endl;
cout<<s[1].x<<endl;
return 0;
}
3.P1217 [USACO1.5] 回文质数 Prime Palindromes
P1217 [USACO1.5] 回文质数 Prime Palindromes - 洛谷
难度:
判断质数:
bool isprime(int a){
if (a<2) return 0;
for(int i=2;i<=int(sqrt(a));i++){
if(a%i==0) return 0;
}
return 1;
}判断回文数:
bool huiwen(int a){
string s=to_string(a);
string t=s;
reverse(t.begin(),t.end());
return s==t;
}
代码部分:
#include <iostream>
#include<algorithm>
#include<cmath>
#include<string>
using namespace std;
bool isprime(int a){
if (a<2) return 0;
for(int i=2;i<=int(sqrt(a));i++){
if(a%i==0) return 0;
}
return 1;
}
bool huiwen(int a){
string s=to_string(a);
string t=s;
reverse(t.begin(),t.end());
return s==t;
}
int main(){
int a,b;
cin>>a>>b;
for(int i=a;i<=b;i++){
if(isprime(i)&&huiwen(i)) cout<<i<<endl;
}
return 0;
}
4.P3406 海底高铁(差分数组+前缀和)
难度:
问题描述:
铁路连接了 N
个城市,每个城市有一个车站。由于各城市间未能协调统一,乘客在经过两个相邻城市之间时,需单独购买该段车票。第 i
段铁路连接城市 i
和城市 i+1
,购买该段铁路的票有两种方式:
- 单程票:每次乘坐需支付
A[i]
元。 - IC卡:需支付
C[i]
元的工本费购买IC卡,之后每次乘坐该段铁路仅需支付B[i]
元。
给定乘客的旅行计划,需计算出使总费用最小的购票策略。一般来说,同一个地方去的次数够多,买IC卡还是比较优惠的。
变量解释:一个N个城市、要去M个城市、按Pi的顺序访问M个城市、Ai是票的原价、Ci是票的开卡费、Bi是票的优惠费。
分析与思路:
为了最小化总费用,局部最优就是全局最优。我们需要统计一下每段铁路的同行次数,再决定哪种方式同行比较好。
1.计算每一段路的经过次数
由于每次访问的城市可能不相邻,即Pi和Pi+1可能隔着好几个城市,而且路径之间还可能存在重叠,那么每次经过这段路时可以记下来这段路的次数。我们想知道 每一段铁路(城市 i 到 i+1)被走了几次,但如果直接对每一段路径都遍历和累加,复杂度会很高(尤其是当路径很长时)。
差分数组的关键思想是:只记录“变化”(一般是加减某个特定的值),然后通过前缀和恢复“完整信息”。前缀和算法(c++版)_c++前缀和-CSDN博客
-
对于每次从城市
P[i]
到P[i+1]
的旅行:-
若
P[i] < P[i+1]
,则在差分数组vis
中:-
vis[P[i]] += 1
;vis[P[i+1]] -= 1(加和后表示从pi--pi+1的城市序列中,每个城市+1)
-
-
若
P[i] > P[i+1]
,则:-
vis[P[i+1]] += 1
;vis[P[i]] -= 1
-
-
然后,对差分数组求前缀和,即可得到每段铁路的实际通行次数。
for (int i = 1; i < N; ++i) {
vis[i] += vis[i-1];
}
2. 计算最小费用
对于每段铁路,计算以下两种方式的费用,并取最小值累加:
-
购买单程票的总费用:
A[i] * 通行次数
-
购买IC卡的总费用:
C[i] + B[i] * 通行次数
代码部分:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int N, M;
cin >> N >> M;
vector<int> P(M + 1);
for (int i = 1; i <= M; ++i) {
cin >> P[i];
}
vector<int> A(N), B(N), C(N);
for (int i = 1; i < N; ++i) {
cin >> A[i] >> B[i] >> C[i];
}
vector<int> vis(N + 1, 0);//差分的数组,方便加某一段的次数
for (int i = 1; i < M; ++i) {
int l = min(P[i], P[i + 1]);
int r = max(P[i], P[i + 1]);
vis[l]++;
vis[r]--;//注意截止位置,假设2-5之间的城市只有三段距离
}
for (int i = 1; i <= N; ++i) {
vis[i] += vis[i - 1];//获得完整路径
}
long long total_cost = 0;
for (int i = 1; i < N; ++i) {
//static_cast<long long>类型转换方式,它将某个值安全地转换为 long long 类型
long long single_ticket_cost = static_cast<long long>(A[i]) * vis[i];
long long ic_card_cost = static_cast<long long>(B[i]) * vis[i] + C[i];
total_cost += min(single_ticket_cost, ic_card_cost);
}
cout << total_cost << endl;
return 0;
}
5.P3375 【模板】KMP
问题描述:
给定两个字符串 S(主串)和 T(模式串),输出:
-
所有模式串 T 在主串 S 中出现的位置(从 1 开始编号)
-
模式串 T 的
next
数组
思路与推导:
先验知识:在模式串匹配过程中,为优化匹配方式而进行的操作。原理分析,进行串的匹配时,模式串比到中间发现不匹配了,下一步应该如何操作呢,对于模式串,前面若有公共前后缀的话,直接让开头前缀的位置放到后缀的位置继续往下比。这样可以直接跳过原来一步步的操作。如下图,
没有公共前后缀的话,只能从头比喽。
下面是手推的写法,之前整理过的。
那么如何求next[]以及nextval[]?就涉及到代码部分了
代码部分:
#include <stdio.h>
#include <string.h>
// 计算模式串 t 的 next 数组
void GetNext(char t[], int next[]) {
int j = 0; // 指向当前正在计算 next[j] 的位置
int k = -1; // 指向 t[0...k-1] 是当前前缀与后缀相等的最大长度
next[0] = -1; // next[0] 总是 -1,表示第一个字符无前缀
// 遍历整个模式串
while (t[j] != '\0') {
if (k == -1 || t[j] == t[k]) {
// 情况1:k==-1 说明回退到底了,或 t[j]==t[k] 表示继续匹配成功
j++;
k++;
next[j] = k; // 记录前缀长度 k 到 next[j]
} else {
// 情况2:失配,回退 k 到 next[k]
k = next[k];
}
}
}
// 计算模式串 t 的 nextval 数组
void GetNextval(char t[], int nextval[]) {
int j = 0; // 正在计算 nextval[j]
int k = -1; // 同样是最长公共前后缀长度指针
nextval[0] = -1; // 初始定义
while (t[j] != '\0') {
if (k == -1 || t[j] == t[k]) {
// 情况1:继续匹配或 k==-1,更新 j 和 k
j++;
k++;
if (t[j] != t[k]) {
// 如果下一个字符不等,则 nextval[j] = k,正常赋值
nextval[j] = k;
} else {
// 如果下一个字符也相等,跳过重复匹配段(优化)
nextval[j] = nextval[k];
}
} else {
// 情况2:失配时回溯 k 到 nextval[k]
k = nextval[k];
}
}
}
可视化每一步操作
#include <iostream>
#include <iomanip>
#include <cstring>
using namespace std;
void GetNext(char t[], int next[]) {
int j = 0;
int k = -1;
next[0] = -1;
cout << "=== GetNext 过程 ===" << endl;
cout << "步骤\tj\tk\tt[j]\tt[k]\t操作\t\t\tnext[j]" << endl;
while (t[j] != '\0') {
if (k == -1 || t[j] == t[k]) {
j++;
k++;
next[j] = k;
cout << setw(2) << j << '\t'
<< setw(2) << j << '\t'
<< setw(2) << k << '\t'
<< (t[j] ? t[j] : '-') << '\t'
<< (t[k] ? t[k] : '-') << '\t'
<< "匹配/初始\t\tnext[" << j << "] = " << k << endl;
} else {
cout << setw(2) << j << '\t'
<< setw(2) << j << '\t'
<< setw(2) << k << '\t'
<< t[j] << '\t'
<< t[k] << '\t'
<< "失配回退\tk = next[" << k << "] = " << next[k] << endl;
k = next[k];
}
}
cout << "====================" << endl << endl;
}
void GetNextval(char t[], int nextval[]) {
int j = 0;
int k = -1;
nextval[0] = -1;
cout << "=== GetNextval 过程 ===" << endl;
cout << "步骤\tj\tk\tt[j]\tt[k]\t操作\t\t\tnextval[j]" << endl;
while (t[j] != '\0') {
if (k == -1 || t[j] == t[k]) {
j++;
k++;
if (t[j] != t[k]) {
nextval[j] = k;
cout << setw(2) << j << '\t'
<< setw(2) << j << '\t'
<< setw(2) << k << '\t'
<< t[j] << '\t'
<< t[k] << '\t'
<< "正常赋值\t\tnextval[" << j << "] = " << k << endl;
} else {
nextval[j] = nextval[k];
cout << setw(2) << j << '\t'
<< setw(2) << j << '\t'
<< setw(2) << k << '\t'
<< t[j] << '\t'
<< t[k] << '\t'
<< "跳过重复\t\tnextval[" << j << "] = nextval[" << k << "] = " << nextval[j] << endl;
}
} else {
cout << setw(2) << j << '\t'
<< setw(2) << j << '\t'
<< setw(2) << k << '\t'
<< t[j] << '\t'
<< t[k] << '\t'
<< "失配回退\tk = nextval[" << k << "] = " << nextval[k] << endl;
k = nextval[k];
}
}
cout << "=======================" << endl << endl;
}
int main() {
char t[] = "abcaabbcabccbaadab";
int next[100], nextval[100];
GetNext(t, next);
GetNextval(t, nextval);
return 0;
}