20230513校内选拔赛
试题源码保存为 \(X.cpp\),\((X为A-K)\)。
A
X++ 是一种非常简单的编程语言,它只有两种操作
- ++操作:X 的值加 \(1\);
- -- 操作: X 的值减 \(1\);
X++ 语言是一种序列语言,仅由若干个单独的语句构成, 语句只含有一种操作和一个变量 X,语句不含空格,只有 "+", "-", "X" 这三种字符。
语句之间仅用换行符隔开,不含其他字符。
现在给你一段 X++ 语言源代码,X 的初始值为 \(0\),求出程序运行结束后,X 的值为多少。
Input
第一行一个正整数 \(n (1 ≤ n ≤ 150)\) — 该源程序的语句数量
接下来 \(n\) 行,每行一个字符串,表示 X++ 语言的一个语句。
请注意:操作可以放在变量的前面和后面,效果是一样的。
Output
输出 X 在程序运行结束后的值
| 输入样例 | 输出样例 |
|---|---|
1 ++X |
1 |
2 X++ --X |
0 |
分析: 如果有 + 这个符号,那么就 +1,否则 -1。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+10, INF=0x3f3f3f3f;
int main(){
int t; string s;
while(cin>>t){
int ans=0;
while(t--) {
cin>>s;
if(s.find('+')!=string::npos)
ans++;
else ans--;
}
cout<<ans<<endl;
}
return 0;
}
B
众所周知一个程序的时间复杂度十分重要,评测机 \(1s\) 大概能做 \(10^8\) 次简单运算,所以根据题目所提供的数据范围你就应该精心的策划好你所写程序应该具有的时间复杂度,而在数据范围变大时,想要在时限内完成同样的一个题目,你就不得不设计出一个更快更巧妙的程序,在本题中我们认定数据范围不一样的题目不是同样的题。
\(Hacker\) 有一个包含 \(n\) 个不同题目的题集 \(A\) ,其中第 \(i (1≤i≤n)\) 个题的数据范围为 \(10^{a_i}\),也就是说第 \(i\) 个题有 \(a_i\) 个 \(0\),数据保证 \(a_i\) 两两不同。
现在他想从题集 \(A\) 里面选出一些题目,然后把选出的题目后面加 \(9\) 个 \(0\),然后加入到一个新题集 \(B\) 里面,但是不能有原题,即这个题数据范围加 \(9\) 个 \(0\) 后的题不能在题集 \(A\) 里面有同样的题,请问所创造的新题集 \(B\) 里面最多能有多少个题。
Input
第一行包含一个整数 \(n(1≤n≤5×10^5)\) — 表示题集 \(A\) 中的题目个数。
第二行包含 \(n\) 个整数 \(a_1, a_2, a_3, ..., a_n(1≤a_i≤10^9)\) — 表示每个题数据范围中 \(0\) 的个数。
Output
输出一行一个整数 — 表示题集 \(B\) 中最多能有多少个题。
| Input | Output |
|---|---|
9 11 4 5 14 1 9 19 8 10 |
6 |
分析: 记录元素是否出现过,如果没有出现过,答案 +1。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+10, INF=0x3f3f3f3f;
int t=1,n,a[N],ans=0;
unordered_map<int,int> mp;
int main(){
while(t--){
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
ans=0, mp.clear();
for(int i=1; i<=n; i++) mp[a[i]] = 1;
for(int i=1; i<=n; i++){
ans += !mp.count(a[i] + 9);
mp[a[i]+9] = 1;
}
printf("%d\n", ans);
}
return 0;
}
C
随着信息化时代的发展,外卖业务成为了一片新蓝海。
其中,饱了没是一支刚刚起步的新秀,为了开拓市场业务,饱了没发放了非常多的红包。
\(Hacker\) 很喜欢点外卖,再他了解到饱了没之后,只恨没有早点发现这个平台,白白损失很多大米。自此,他非常热衷于收集饱了没的各类满减红包。现在他已经选好了一些订单,并且希望在结算的时候花掉一部分满减红包,使自己尽可能少的花钱。显然,每个红包只能在至多一个订单结算中使用,也可以不被使用,每个订单结算的时候只能使用至多一个红包。在这种情况下,他想知道最少需要花费多少钱。
满减红包是在满足满减金额要求的时候获得满减红包面额的优惠。比如 \(50\) 元的待支付订单,可以使用满 \(50\) 减 \(20\) 的红包,然后该订单仅需支付 \(30\) 元,但是该订单不能使用满 \(60\) 减 \(30\) 的红包。
Input
第一行两个正整数 \(n,m\),表示有 \(n\) 个待结算订单 \(m\) 个饱了没红包\((1≤n,m≤10^5)\);
第二行 \(n\) 个正整数 \(w\),\(w_i\) 表示第 \(i\) 个待结算订单的金额 \((1≤w_i≤10^5)\);
接下来 \(m\) 行,每行两个正整数 \(x,y\) 表示,该满减红包是满 \(x\) 减 \(y\) 的红包 \((1≤y≤x≤10^5)\)。
Output
输出一个正整数,表示最小花费的金额。
| Input | Output |
|---|---|
5 5 3 2 | 2 |
7 5 5 2 | 2 |
分析: 考虑将红包的减免最大化,对 \(w_i\) 升序排序,对红包按照关键字 \({x,y}\) 的顺序升序排序 。
对于每一个 \(w_i\) 考虑一个合适的红包,该红包满足 \(w_i ≥ x\) 且 \(y\) 最大,可以通过一个堆进行维护。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10, INF=0x3f3f3f3f;
int t=1,n,m,w[N],x,y;
pair<int,int> tt[N];
int main(){
while(t--){
scanf("%d%d", &n,&m);
for(int i=1; i<=n; i++) scanf("%d", &w[i]);
for(int i=1; i<=m; i++){
scanf("%d%d", &x,&y), tt[i]={x,y};
}
sort(w+1, w+1+n);
sort(tt+1, tt+1+m);
LL ans=0;
priority_queue<int> q;
for(int i=1,j=1; i<=n; i++){
while(j <=m && w[i] >= tt[j].first)
q.push(tt[j++].second);
if(q.size()) ans -= q.top(), q.pop();
ans += w[i];
}
printf("%lld\n", ans);
}
return 0;
}
D
\(Hacker\) 有一条丝带,它的长度是 \(n\),他希望以满足以下两个条件的方式剪彩:
- 剪彩后,每段丝带的长度应为 \(a,b,c\) 其中一个。
- 剪彩后的丝带段数应为最大。
帮助阳阳找到剪彩后的最大丝带段数。
Input
第一行包含四个空格分隔的整数 \(n,a,b,c (1 ≤ n, a, b, c ≤ 4000)\),分别对应原始丝带的长度和剪彩后丝带的可接受长度。
Output
输出一个数字,表示可能的丝带段数的最大值。
输入输出数据保证,至少存在一种合法的剪彩方式。
| Input | Output |
|---|---|
5 5 3 2 |
2 |
7 5 5 2 |
2 |
在第一个示例中,可以这样切割丝带:第一块长度为 \(2\),第二块长度为 \(3\)。
在第二个例子中,可以这样切割丝带:第一块长度为 \(5\),第二块长度为 \(2\)。
分析:
解法 \(1\):二分答案,求右边界,\(chk(x)\) 检查 当答案为 \(x\) 时是否合法。
假设 \(a,b,c\) 的数量分别为 \(i,j,k\),于是有:
枚举 \(i,j\),二分 \(x\),复杂度 \(O(n^2logn)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=4e3+10, INF=0x3f3f3f3f;
int n,a,b,c;
bool chk(int x){
for(int i=0; i<=n/a; i++)
for(int j=0; j<=n/b; j++){
int k = (n-a*i - b*j)/c;
if(k>=0 && a*i + b*j+c*k==n && i+j+k>=x) return 1;
}
return 0;
}
int main(){
while(cin>>n>>a>>b>>c){
int l=1,r=4e3;
while(l<r){
int mid = l+r+1 >>1;
if(chk(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
return 0;
}
解法 \(2\):思路同解法 \(1\),暴力枚举 \(i,j\),同时更新答案,复杂度 \(O(n^2)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=4e3+10, INF=0x3f3f3f3f;
int n,a,b,c;
int main(){
while(cin>>n>>a>>b>>c){
int ans=0;
for(int i=0; i<=n/a; i++)
for(int j=0; j<=n/b; j++){
int k = (n-a*i-b*j)/c;
if(k>=0 && a*i + b*j+c*k==n){
ans = max(ans, i+j+k);
}
}
cout<<ans<<endl;
}
return 0;
}
解法 \(3\):完全背包,将问题看成给出三种物品,问恰好能够装满背包的最大价值。
定义状态:\(dp[i][j]\) 表示只选择前 \(i\) 个物品装满背包容量为 \(j\) 的情况。
状态转移:对于第 \(i\) 个物品,可以选或不选,\(dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]]+1);\)
状态压缩:\(dp[j]=max(dp[j], dp[j-v[i]]+1);\)
初始化:\(dp[0]=0, dp[others]=-inf\);
目标:\(dp[n]\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=4e3+10, INF=0x3f3f3f3f;
int v[5], dp[N];
int main(){
while(cin>>n>>v[1]>>v[2]>>v[3]){
memset(dp,-1,sizeof(dp));
dp[0]=0;
for(int i=1; i<=3; i++)
for(int j=v[i]; j<=n; j++)
dp[j] = max(dp[j], dp[j-v[i]]+1);
cout<<dp[n]<<endl;
}
return 0;
}
E
\(Hacker\) 发现了个 \(N×N\) 的正方形网格,每个网格上都有一个数字,第 \(i\) 行第 \(j\) 列的数是 \(a_{i,j}\)。
他想从这个网格中选择一个 \(K×K\) 的正方形子网格,使得这个子网格中第 \(floor(K×K/2)+1\) 大的数最小。
因为他今天感冒了,状态不是很好,请大家帮助欧队找到这个数。
\(floor()\) 的意思是下取整,例如:\(floor(2.99) = 2, floor(4.00) = 4\);
当 \(K=2\) 时,\(floor(K*K/2) = floor(2) = 2\);
当 \(K=3\) 时,\(floor(K*K/2) = floor(4.5) = 4;\)
Input
第一行输入两个整数 \(N,K\);
接下来输入一个 \(N×N\) 的正方形网格,每个网格上都有一个数字,第 \(i\) 行第 \(j\) 列的数是 \(a_{i,j}\)。
\(1≤N≤800, 1≤K≤N, 0≤a_{i,j}≤10^9\)。
所有的输入数据都是整数
Output
找到某个 \(K×K\) 的子网格, 它的第 \(floor(K×K/2)+1\) 大的数是所有 \(K×K\) 子网格中最小的,输出这个最小的数.
| Input | Output |
|---|---|
3 2 1 7 0 5 8 11 10 4 2 | 4 |
3 3 1 2 3 4 5 6 7 8 9 | 5 |
样例1解释:要从 \(3*3\) 的网格中选取一个 \(2*2\) 的子网格有 \(4\) 种选法,分别是:
四个子网格对应的第 \(3\) 大的数分别是 \(5, 7, 5, 4\)。 最小的数是 \(4\),所以输出 \(4\)。
分析: 最大值最小,明显二分答案,\(chk(x)\) 检查当答案为 \(x\) 时是否合法,可以利用二维前缀和优化。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=810, INF=0x3f3f3f3f;
int n,m,k,a[N][N],s[N][N];
// chk(x) 检查是否存在 k*k 中第 m 大的数最小为 x。
bool chk(int x){
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
s[i][j] =s[i-1][j] +s[i][j-1] -s[i-1][j-1] +(a[i][j]>x);
for(int i=k; i<=n; i++)
for(int j=k; j<=n; j++){
int ss =s[i][j] -s[i-k][j] -s[i][j-k] +s[i-k][j-k];
if(ss < k*k/2+1) return 1;
}
return 0;
}
int main(){
while(~scanf("%d%d",&n,&k)){
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++) scanf("%d",&a[i][j]);
int l=0, r=1e9;
while(l<r){
int mid = l+r >> 1;
if(chk(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",r);
}
return 0;
}
F
现有一个 \(n * m\) 个单元的矩形空间,每一个空间只可能是可通过的或者不可通过的。如果对应位置标注为 ‘*’ 代表该空间不可通过,若为 ‘.’ 则代表可以通过。现在小明有一个神奇的问题。想问你,如果对于每个不可通过的单元,暂时将其置为可通过,那么包括其本身在内,和他联通的区域的大小是多少(注意此时其他不可通过的单元仍然不可通过)。
Input
输入第一行包含两个整数 \(n,m(1 ≤ n, m ≤ 1000)\),代表矩形空间的长和宽。
接下来 \(n\) 行每行 \(m\) 个字符,若为 ‘*’ 代表不可通过,为 . 代表可通过。
Output
再次输出这个矩形空间,对于每个原本为 ‘*’ 的点,输出其暂时可通过后联通区域的大小,为了方便输出,将这个值对 \(10\) 取余后输出。对于原本为‘.’的点仍然维持原来的状态。
| Input | Output |
|---|---|
3 3 *.* .*. *.* | 3.3 .5. 3.3 |
4 5 **..* ..*** .*.*. *.*.* | 46..3 ..732 .6.4. 5.4.3 |
样例 \(1\) 解释: 周围四个 \(*\) 如果联通,都联通了其周围的两个点,加上自己是 \(3\),而中间的点则联通了上下左右 \(4\) 个点,所以总共是 \(5\)。
分析
明显dfs/bfs,但是仔细一想如果 \(n^2\) 个点 都bfs,复杂度最高 \(O(n^4)\),会TLE.
可以发现最多的耗时是每次bfs联通块,何不直接记录连通块中数量。
最后判断每个 * 四周的连通块即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1010, INF=0x3f3f3f3f;
int n,m,k,d[][2]={-1,0,0,1,1,0,0,-1};
char s[N][N];
int cs[N][N], cnt[N*N];
bool in(int x,int y){
return x>=0 && x<n && y>=0 && y<m;
}
void bfs(int sx,int sy){
queue<pair<int,int> > q; q.push({sx,sy});
int ans=1; cs[sx][sy] = ++k;
while(q.size()){
auto u=q.front(); q.pop();
int x=u.first, y=u.second;
for(int i=0; i<4; i++){
int a=x+d[i][0], b=y+d[i][1];
if(a<0||a>=n||b<0||b>=m) continue;
if(cs[a][b] || s[a][b]!='.') continue;
q.push({a,b}), ans=(ans+1);
cs[a][b] = k;
}
}
cnt[ k ] = ans;
}
void init(){
k=0;
memset(cs,0,sizeof(cs));
memset(cnt,0,sizeof(cnt));
}
int main(){
while(~scanf("%d%d", &n,&m)){
init();
for(int i=0; i<n; i++) scanf("%s",s[i]);
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
if(s[i][j]=='.' && !cs[i][j]) bfs(i,j);
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
if(s[i][j]=='*'){
map<int,int> ha; int ans=1;
for(int k=0; k<4; k++){
int a=i+d[k][0], b=j+d[k][1];
if(in(a,b) && s[a][b]=='.') {
int kk = cs[a][b];
if(! ha.count( kk ))
ha[ kk ]=1, ans=(ans+cnt[ kk ]) %10;
}
}
s[i][j]=ans+'0';
}
for(int i=0; i<n; i++) puts(s[i]);
}
return 0;
}
G
给你一个括号序列,求一个区间内的括号完全匹配的最长子序列长度。
注:括号序列即是一个只由左括号 “ (” 和右括号 “)”构成的序列,括号匹配即是一个左括号与其右边的还未被匹配的右括号组成一对。而一个括号序列完全匹配,则是指一个括号序列中每个括号都有一个括号与之匹配。例,“(())”是一个完全匹配的括号序列,而“(()))”则不是,因为他有一个右括号不与其他括号匹配。
一个序列的子序列即是通过删除该序列中的几个元素可以得到的序列。
Input
第一行给出一个由”(“和”)“组成的字符串 \(s(1≤∣s∣≤10^6)\)。
第二行为一个整数 \(Q\),表示询问的个数 \((1 ≤ Q ≤ 10^5)\)。
接下来 \(Q\) 行,每行两个整数 \(l_i,r_i(1 ≤ l_i≤ r_i≤ n)\),表示询问的区间。
Output
对于每个询问,输出一行答案。
| Input | Output |
|---|---|
())(())(())( 7 1 1 2 3 1 2 1 12 8 12 5 11 2 10 | 0 0 2 10 4 6 6 |
分析
感觉是个树状数组,线段树
H
众所周知,张四是目前实验室里的最强。
张四初始的经验值是 \(s\),现在有 \(n\) 道题,题目的序号是 \(i(1≤i≤n)\),写第 \(i\) 题需要大于\(x_i\) 的经验值,但写完第 \(i\) 题便可获得 \(y_i\)的经验值。
请问张四是否可以写完全部题目。
Input
第一行包含初始经验值 \(s\) 和题目数量 \(n (1≤s≤10^4, 1≤n≤10^3)\)。
接下来 \(n\) 行包含: 第 \(i\) 题的 \(x_i\) 和 \(y_i (1≤x_i≤10^4,0<y_i≤10^4)\)。
Output
可以做完输出 "YES" ,不可以输出 "NO" (输出不包含引号)。
| Input | Output |
|---|---|
2 2 1 99 100 0 | YES |
10 1 100 100 | NO |
分析: 直接按照 \(x\) 升序,先写当前可以完成的题目。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e4+10, INF=0x3f3f3f3f;
int s,n,m,x,y;
pair<int,int> tt[N];
int main(){
while(~scanf("%d%d", &s,&n)){
for(int i=1; i<=n; i++)
scanf("%d%d", &x,&y), tt[i]={x,y};
sort(tt+1, tt+1+n);
for(int i=1; i<=n; i++){
if(s > tt[i].first) s += tt[i].second;
else{
s=0; break;
}
}
printf("%s\n",s?"YES":"NO");
}
return 0;
}
I
有一款游戏叫做地铁逃亡,游戏的主角位于隧道的最左端,想要走到隧道的最右端,隧道是一个有三行和 \(n\) 列组成的矩阵,游戏开始时,一些列车将会驶向主角,每列货车由两个或者两个以上的相邻单元格组成。
所有的列车都是以每秒两个单元格的速度从右向左行驶,而我们的主角以每秒一个单元格的速度从左往右行驶,为了简单起见,游戏的实现是让主角和列车轮流移动,首先,主角向右移动一个单元格,然后向上移动一个单元格或者向下移动一个单元格或者不动,然后所有的列成同时向左移动两个单元格,因此在一次移动中,主角肯定会向右移动一次单元格并且可以向上或者向下移动,如果主角在任何时候与列车在同一个单元格中那么游戏失败,如果列车达到了最左边列车会继续向前行驶一直到离开隧道,你的任务是回答主角是否可以通过游戏安全达到最右边。
Input
每个测试包含一到十组输入数据。每组测试的第一行输入一个整数 \(t (1 ≤ t ≤ 10)\) 表示输入数据的个数。
接下来输入 \(t\) 组测试数据
每组测试数据的第一行输入两个整数 \(n,k (2 ≤ n ≤ 100, 1 ≤ k≤ 26)\) 表示隧道的长度以及列车的数量。
接下来三行数据每一行都包含 \(n\) 个字符,主角的初始位置标记为s在最左边一列,每一辆列车都是由一些相同的大写英文字母组成排列在同一行上,不同的列车用不同的英文字母表示,· 表示空的单元格。
Output
如果可以过关则输出 "YES",否则输出 “NO”。
| Input | Output |
|---|---|
2 16 4 ...AAAAA........ s.BBB......CCCCC ........DDDDD... 16 4 ...AAAAA........ s.BBB....CCCCC.. .......DDDDD.... | YES NO |
2 10 4 s.ZZ...... .....AAABB .YYYYYY... 10 4 s.ZZ...... ....AAAABB .YYYYYY... | YES NO |
J
老王非常喜欢国足。一天,他要去观看一场足球比赛。由于老王很穷,并且没有车子,所以他能骑共享单车。这个城市有 \(n\) 个路口,其中一些道路由双向连接。每路口之间的长度由若干米的正整数定义,路口之间可以有不同的长度。
最初,每个路口只有一辆共享单车停在那里。从第 i 个路口出发,由于每个共享单车电量不唯一,即所行驶距离不超过 \(t_i\) 米,老王骑车(可能要经过几个中间路口)到其他路口。 此外,乘坐的费用不取决于距离,而是等于 \(c_i\) 元。由于道路中间没有共享单车,即老王不能停在路中间。每辆单车只能使用一次,开始老王只能从他最初所在的路口使用共享单车到其他路口(随后可以多次换乘)。
目前老王位于 \(x\) 路口,足球场位于 \(y\) 路口,请你帮助老王确定骑车到球场需要的最低金额。
Input
第一行输入包含 \(2\) 个整数 \(n,m (1 ≤ n ≤ 1000, 0 ≤ m ≤ 1000)\),分别对应的是城市中路口与路口之间道路的数量。路口编号范围是从 \(1\) 到 \(n\)。
接下来的一行包含 \(2\) 个整数 \(x, y (1 ≤ x, y ≤ n)\),它们分别是初始路口和终点路口的编号。
接下来的 \(m\) 行包含道路的描述。 每条路由 \(3\) 个整数描述:\(u_i, v_i, w_i (1 ≤ u_i, v_i ≤ n, 1 ≤ w_i ≤ 10^9)\),分别代表道路连接的路口编号和道路长度。
接下来的 \(n\) 行包含 \(n\) 对整数 \(t_i,c_i (1 ≤ ti, ci ≤ 10^9)\),描述了在第 \(i\) 个路口的共享单车所能到达的最远距离和所需要花费的金额。
一条路不可能只连接 \(1\) 个路口,但是在一对路口之间可以有不止一条路。
每一行中的所有连续数字都由一个空格字符分隔。
Output
如果老王不能到达终点,输出 \(-1\),否则,输出最少花费的金额。
在C++中请不要用 %lld 说明符读取或写入 \(64\) 位整数,推荐使用标准输入输出流 cin, cout 或者 %I64d 说明符。
| Input | Output |
|---|---|
4 4 1 3 1 2 3 1 4 1 2 4 1 2 3 5 2 7 7 2 1 2 7 7 |
9 |
最佳路径:从路口 1 到路口 2 (经过路口 4),然后从路口 2 到路口3,共花费 7+2=9 元。
分析: 还没开始
K
一个女孩捡到了 \(n(n≤100)\) 片树叶,每个树叶有两个属性字符串 \(a,b\)。
对于两个叶子,如果它们的 \(a\) 不相同或者 \(b\) 不相同,就认为这两个叶子是不相同的叶子,否则就是相同的。
请你求出,\(n\) 片树叶中有多少不同的叶子。
Input
第一行一个整数 \(n\)。接下来 \(n\) 行,每行两个字符串 \(a,b\)。
Output
输出不同的树叶的个数
| Input | Output |
|---|---|
5 birch yellow maple red birch yellow maple yellow maple green |
4 |
3 oak yellow oak yellow oak yellow |
1 |
分析: 直接哈希即可
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
map<pair<string, string>, int> mp;
string a, b;
int n;
int main() {
while (cin >> n) {
mp.clear();
while (n--) {
cin >> a >> b;
mp[make_pair(a, b)] = 1;
}
cout << mp.size() << endl;
}
return 0;
}

浙公网安备 33010602011771号