参考:
https://www.luogu.com.cn/problem/P5660
https://www.luogu.com.cn/problem/P5661
总结
本系列为CSP-J/S算法竞赛真题讲解,会按照年份分析每年的真题,并给出对应的答案。本文为2019年真题。
https://www.luogu.com.cn/problem/list?tag=343&page=1
[CSP-J2019] 数字游戏
题目描述
小 K 同学向小 P 同学发送了一个长度为 8 88 的 01 字符串来玩数字游戏,小 P 同学想要知道字符串中究竟有多少个 1 11。
注意:01 字符串为每一个字符是 0 00 或者 1 11 的字符串,如“101”(不含双引号)为一个长度为 3 33 的 01 字符串。
输入格式
输入文件只有一行,一个长度为 8 88 的 01 字符串 s ss。
输出格式
输出文件只有一行,包含一个整数,即 01 字符串中字符 1 \bm 11 的个数。
样例 #1
样例输入 #1
00010100
样例输出 #1
2
样例 #2
样例输入 #2
11111111
样例输出 #2
8
提示
【输入输出样例 1 说明】
该 01 字符串中有 2 22 个字符 1 11。
【输入输出样例 2 说明】
该 01 字符串中有 8 88 个字符 1 11。
【数据规模与约定】
- 对于 20 % 20\%20% 的数据,保证输入的字符全部为 0 00。
- 对于 100 % 100\%100% 的数据,输入只可能包含字符 0 00 和字符 1 11,字符串长度固定为 8 88。
答案
//#include <bits/stdc++.h> #include<cstdio>//必须包含cstdio头文件 using namespace std; int main(){ //freopen("candy.in","r",stdin); //freopen("candy.out","w",stdout); char x; int ans=0; for(int i=1;i<=8;i++) { scanf("%c",&x); if(x=='1') ans++; } printf("%d\n",ans); return 0; }
输出:
答案2
#include<iostream> using namespace std; int ans=0;//ans计数器 char s[21];//字符串开大一点 int main(){ cin>>s; for(int i=0;i<8;i++){//模拟 if(s[i]=='1'){//判断是否为1 ans++;//计数器++ } } cout<<ans<<endl; return 0;//完美结束 }
输出为:
[CSP-J 2019] 公交换乘
题目描述
著名旅游城市 B 市为了鼓励大家采用公共交通方式出行,推出了一种地铁换乘公交车的优惠方案:
- 在搭乘一次地铁后可以获得一张优惠票,有效期为 45 分钟,在有效期内可以消耗这张优惠票,免费搭乘一次票价不超过地铁票价的公交车。在有效期内指开始乘公交车的时间与开始乘地铁的时间之差小于等于 45 分钟,即:
t b u s − t s u b w a y ≤ 45 t_{bus} - t_{subway} \leq 45tbus−tsubway≤45 - 搭乘地铁获得的优惠票可以累积,即可以连续搭乘若干次地铁后再连续使用优惠票搭乘公交车。
- 搭乘公交车时,如果可以使用优惠票一定会使用优惠票;如果有多张优惠票满足条件,则优先消耗获得最早的优惠票。
现在你得到了小轩最近的公共交通出行记录,你能帮他算算他的花费吗?
输入格式
输入文件的第一行包含一个正整数 n nn,代表乘车记录的数量。
接下来的 n nn 行,每行包含 3 个整数,相邻两数之间以一个空格分隔。第 i ii 行的第 1 个整数代表第 i ii 条记录乘坐的交通工具,0 代表地铁,1 代表公交车;第 2 个整数代表第 i ii 条记录乘车的票价 p r i c e i price_ipricei ;第三个整数代表第 i ii 条记录开始乘车的时间 t i t_iti(距 0 时刻的分钟数)。
我们保证出行记录是按照开始乘车的时间顺序给出的,且不会有两次乘车记录出现在同一分钟。
输出格式
输出文件有一行,包含一个正整数,代表小轩出行的总花费。
样例 #1
样例输入 #1
6 0 10 3 1 5 46 0 12 50 1 3 96 0 5 110 1 6 135
样例输出 #1
36
样例 #2
样例输入 #2
6 0 5 1 0 20 16 0 7 23 1 18 31 1 4 38 1 7 68
样例输出 #2
32
提示
【输入输出样例 1 说明】
第一条记录,在第 3 分钟花费 10 元乘坐地铁。
第二条记录,在第 46 分钟乘坐公交车,可以使用第一条记录中乘坐地铁获得的优惠票,因此没有花费。
第三条记录,在第 50 分钟花费 12 元乘坐地铁。
第四条记录,在第 96 分钟乘坐公交车,由于距离第三条记录中乘坐地铁已超过 45 分钟,所以优惠票已失效,花费 3 元乘坐公交车。
第五条记录,在第 110 分钟花费 5 元乘坐地铁。
第六条记录,在第 135 分钟乘坐公交车,由于此时手中只有第五条记录中乘坐地铁获得的优惠票有效,而本次公交车的票价为 6 元,高于第五条记录中地铁的票价 5 元,所以不能使用优惠票,花费 6 元乘坐公交车。
总共花费 36 元。
【输入输出样例 2 说明】
第一条记录,在第 1 分钟花费 5 元乘坐地铁。
第二条记录,在第 16 分钟花费 20 元乘坐地铁。
第三条记录,在第 23 分钟花费 7 元乘坐地铁。
第四条记录,在第 31 分钟乘坐公交车,此时只有第二条记录中乘坐的地铁票价高于本次公交车票价,所以使用第二条记录中乘坐地铁获得的优惠票。
第五条记录,在第 38 分钟乘坐公交车,此时第一条和第三条记录中乘坐地铁获得的优惠票都可以使用,使用获得最早的优惠票,即第一条记录中乘坐地铁获得的优惠票。
第六条记录,在第 68 分钟乘坐公交车,使用第三条记录中乘坐地铁获得的优惠票。
总共花费 32 元。
【数据规模与约定】
对于 30 % 30\%30% 的数据,n ≤ 1000 n \leq 1000n≤1000,t i ≤ 1 0 6 t_i \leq 10^6ti≤106。
另有 15 % 15\%15% 的数据,t i ≤ 1 0 7 t_i \leq 10^7ti≤107,p r i c e i price_ipricei 都相等。
另有 15 % 15\%15% 的数据,t i ≤ 1 0 9 t_i \leq 10^9ti≤109,p r i c e i price_ipricei 都相等。
对于 100 % 100\%100% 的数据,n ≤ 1 0 5 n \leq 10^5n≤105,t i ≤ 1 0 9 t_i \leq 10^9ti≤109,1 ≤ p r i c e i ≤ 1000 1 \leq price_i \leq 10001≤pricei≤1000。
答案1
#include<bits/stdc++.h> using namespace std; //乘车记录的数量(n) int n; // 交通工具(g)、票价(p)、时间(t) int g[100010],p[100010],t[100010]; int sum=0;//总花费(sum) int ques=0; //优惠票总数(ques) queue<int> q1;//优惠票时间队列(q1) queue<int> q2;//优惠票价钱队列(q1) int main() { cin>>n;// 记录条数n for(int i=1; i<=n; i++) { // 记录交通工具(g)、票价(p)、时间(t) cin>>g[i]>>p[i]>>t[i]; // 判断地铁 if (g[i] ==0) { //因为地铁一定要花钱,所以将总花费(sum)加上票价(p) //坐地铁一定会拿到一张优惠票, //我们将优惠票的时间压入时间队列(q1), //将票价压入价钱队列(q2),并将优惠票的数量(ques)加1 sum += p[i]; q1.push(t[i]); q2.push(p[i]); ques++; } // 筛出废票 //当优惠票超过45分后,该优惠票就会失效, //因为队列FIFO的特性,只要队首的那张没超时,就代表后面的都没超时。 //因此使用while循环(附加非空判定以防万一) while(t[i]-q1.front()>45 && !q1.empty()) { //只要循环进去了,说明这张票废了, // 把两个队列(q1q2)的队首都弹出,并把优惠票数量(ques)减1 q1.pop(); q2.pop(); ques--; } int f=0;//优惠票使用状态 int quesd = ques;//优惠票备用数量quesd //判断公交 if(g[i]==1) { //接下来为保险起见分为有优惠票(优惠票队列(q1q2)非空)和 //无优惠票(优惠票队列(q1q2)为空)两种情况考虑。先写出判断语句 if(!q1.empty()) { /*接下来进入重头戏,就是当有优惠票时,因为优惠票可能会不能用, 而且没有规律(不像时间队列(q1)有规律),在这里所有的优惠票肯定都不会超时(超时的全弹出去了)。 我们在这里使用这样的方法: 把队列(q1q2)从头到尾检索一次,即使已经有符合的了也要全检索完(因为优惠票需要按时间顺序排列,其顺序不能更改)。 如何检索呢?只需要把队首压入队尾,再把队首弹出就可以了,这样第一位就会变到最后一位,第二位变到第一位,以此类推。 当全部走完后,第一位又会变回第一位。 */ //首先,进行循环 /*为什么这里要用备用数量(quesd)呢?因为假设有优惠票被使用,那么此票就会被弹出,数量(ques)会减1(参见上文第四大步), 因为数量会变,所以需要存一个固定的值,也就是quesd 紧接着分两种情况,一种是该票可以使用,一种是该票不能使用。 可以使用即是价钱队列(q2)的队首大于等于乘坐此次公交所花的钱,不可以使用则反之。 为判断方便,以可以使用为条件,不可以使用为该条判断的else */ if(f ==0 && p[i]<=q2.front()) { /*此处的使用状态(f)为后期进行判定时使用,f为0表示本轮还没有使用优惠票,f为1表示本轮使用了优惠票 当满足条件时,表示可以使用优惠票,f的值改为1,并将该票弹出队列(q1q2),优惠票数量(ques)减1 */ f=1; q1.pop(); q2.pop(); ques--; } else { q1.push(q1.front()); q2.push(q2.front()); q1.pop(); q2.pop(); } } else { //当无优惠票时,无论如何都要花钱, //else里面将总花费(sum)加上票价(p)。 sum += p[i]; continue; } if ( f == 0 ) { sum += p[i] ; } } } cout<<sum; return 0;//完美结束 }
输出为:
答案2
#include <iostream> using namespace std; const int MAXN = 100005; struct Ticket { //赠票的价格,最晚使用时间和是否需用过 int price, time, used; } q[MAXN];//赠票盒子 int head, tail, n, cost; int main() { cin >> n; for (int i = 0; i < n; ++i) { int op, price, time; //输入每次坐车的种类,价格和发车时间 cin >> op >> price >> time; if (op == 0) { //如果是坐地铁,直接把价格加到cost里面 cost += price; //新一张赠票插入数组末尾,这张票的最晚使用时间是当前时间+45 q[tail].time = time + 45; //赠票面额就是地铁票价 q[tail++].price = price; } else { //先用一个循环把过期票扔掉 while (head < tail && q[head].time < time) { head++; } bool found = false;//表示是否有合适的赠票,先假设没有 for (int j = head; j < tail; ++j) { //循环所有剩余的票,这些一定都没过期,因为题目中时间是按顺序给我们的 if (q[j].price >= price && q[j].used == 0) { //如果价格合适,并且没用过,标记找到了,这张票标记用过 found = true; q[j].used = 1; break; } } //如果没找到合适的赠票,老老实实花钱买吧 if (!found) cost += price; } } cout << cost << endl; return 0; }
输出为:
现场真题注意事项
https://cspoj.com/contest.php?cid=1002
Fus5yz4x3EcSJH1Z
注意事项
- 文件名(程序名和输入输出文件名)必须使用英文小写。(提交必须使用freopen()进行提交)
- C/C++ 中函数 main() 的返回值类型必须是 int,程序正常结束时的返回值必须是0。
- 提交的程序代码文件的放置位置请参考各省的具体要求。
- 因违反以上三点而出现的错误或问题,申述时一律不予受理。
- 若无特殊说明,结果的比较方式为全文比较(过滤行末空格及文末回车)。
- 程序可使用的栈空间内存限制与题目的内存限制一致。
- 全国统一评测时采用的机器配置为:Inter® Core™ i7-8700K CPU @3.70GHz,内存 32GB。上述时限以此配置为准。
- 只提供 Linux 格式附加样例文件。
- 评测在当前最新公布的 NOI Linux 下进行,各语言的编译器版本以此为准
/*
假设输入样例数据存在文件test.in中,输出样例数据存在文件test.out中,
则在CSP、NOI等比赛的代码中,需添加freopen、fclose语句,
内容详见模板代码如下。
*/
#include <bits/stdc++.h> #include<cstdio>//必须包含cstdio头文件 #include<iostream> using namespace std; int main(){ freopen("test.in","r",stdin); freopen("test.out","w",stdout); cout<<"Hello NOI"<<endl; fclose(stdin); fclose(stdout); return 0; }
下面为函数的简介,详细可参见 http://www.cplusplus.com/reference/clibrary/cstdio/freopen.html
函数名:freopen
声明:FILE *freopen( const char *path, const char *mode, FILE *stream );
所在文件: stdio.h
参数说明:
path: 文件名,用于存储输入输出的自定义文件名。
mode: 文件打开的模式。和fopen中的模式(如r-只读, w-写)相同。
stream: 一个文件,通常使用标准流文件。
返回值:成功,则返回一个path所指定文件的指针;失败,返回NULL。(一般可以不使用它的返回值)
功能:实现重定向,把预定义的标准流文件定向到由path指定的文件中。标准流文件具体是指stdin、stdout和stderr。其中stdin是标准输入流,默认为键盘;stdout是标准输出流,默认为屏幕;stderr是标准错误流,一般把屏幕设为默认。通过调用freopen,就可以修改标准流文件的默认值,实现重定向。
#include<iostream> #include<cstdio> using namespace std; int main(){ freopen("7532.in", "r", stdin); freopen("7532.out", "w", stdout); //原来的代码保持不变 double a, b, r; int k; cin >> a >> b; k = int(a/b); r = a - b * k; printf("%g", r); //------------- fclose(stdin); fclose(stdout); return 0; }