前言
我感觉这次选拔赛选拔只是一方面,更重要的一方面是让大家当做一次练习,即使比赛发挥的不好,也不要灰心,距离蓝桥杯省赛还有3个多月时间,期间如果好好学习,多多练习,一定会有很大的提高,也一定会在比赛中取得好成绩。这次编程题,除了第一题,其他题的出题思路是,让大多数人都能得分,但得不满分。主要目的是想让大家明白,这种比赛模式下,只要按照题意写了代码,就能得到部分的分数,但想要得满分,还需要算法和数据结构来进行优化。也是想让大家认识到算法与数据结构的作用。
1. 二进制
题目:
请写出int类型数20201209的二进制数。提示:答案的长度应为32位。
答案:
00000001001101000011111011111001
题解:
主要考察进制转换,将10进制转换为2进制,每次对2取模,并将这个数除以2,直到这个数变成0为止。不过需要注意的是32位,所以求的不够32位的,需要在前面补0。java的话可以直接调用相关的方法即可转换。
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
string ans = "";
int n = 20211209;
for(int i = 1; i <= 32; i++){
ans = (char) (n%2+'0') + ans;
n = n/2;
}
cout << ans;
}
当然由于是填空题,也可以自行手算,按照10进制与2进制的关系推,也很容易得出正确答案。但还有一个最简单快速方法使用电脑自带的计算器,切换成程序员模式…
2. 日期计算
题目:
Hhy作为一名资深淀粉,他非常喜欢7这个数字,他想知道从EDG成立到夺得2021年S11全球总决赛冠军的这些天中(包括建队与夺冠这两天),年月日中一共出现了多少次7这个数字,比如2017年7月17日这一天共出现了3次7。
提示:
EDG电子竞技俱乐部(EDward Gaming),简称EDG,是一家中国电子竞技俱乐部,于2013年9月13日在广州成立。
2015年5月11日,在美国佛罗里达州塔拉哈西的MSI季中赛总决赛中,EDG以3:2战胜韩国战队SKT1,夺得英雄联盟季中赛世界冠军。
2021年11月7日,在冰岛雷克雅未克的英雄联盟全球总决赛中,EDG战队3:2战胜DK战队夺LPL第三座S赛冠军。
答案:
907
题解
题目求2013年9月13日到2021年11月7日中间出现过多少次7。可以通过编程来解决,下面的代码也是一个求日期问题的标准代码模板,不太会的同学可以参考一下。
但其实可以很容易发现,本题中7的出现次数与是不是闰年没有关系,并且出现次数的有一定的规律,比如年份中只有2017这一年包含着7,月份中只有7月包含着7,天中只要7,17,27这三天包含着7。因此即便不会用代码计算,也可以很容易的找规律的出。(如果不会编程还没找到规律,一个一个数也是一种办法…)
代码:
#include<bits/stdc++.h>
using namespace std;
int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
int jud(int f){
int ans = 0;
while(f){
if(f%10 == 7) ans++;
f = f/10;
}
return ans;
}
int main(){
int y1 = 2013, m1 = 9, d1 = 13;
int y2 = 2021, m2 = 11, d2 = 7;
int ans = jud(y1) + jud(m1) + jud(d1), f = 5;
while(1){
if(y1%4 == 0 && y1%100!=0 || y1%400 == 0) month[2] = 29;
d1++;
if(d1 > month[m1]){
d1 = 1; m1++;
if(m1 > 12){
m1 = 1; y1++;
}
}
ans += jud(y1) + jud(m1) + jud(d1);
month[2] = 28;
if(y1 == y2 && m1 == m2 && d1 == d2) break;
}
cout << ans;
}
3 杨辉三角
题目:
杨辉三角,是二项式系数在三角形中的一种几何排列。他有很多优美的性质。其中一条可以非常容易发现,他的每个数等于它上方两数之和。求出杨辉三角中第123456行,从左到右数第123453个数的值。
答案:
472514398
题解:
这道题有很多方法可以解决。
一、可以按照题目中给出的性质一行一行的推出最后的结果。需要注意的是如果开一个[123457][123457]的二维数组,会导致空间复杂度太大,程序运行不了。可以观察到每次计算时只需使用当前行和上一行的数组,因此可以使用滚动数组优化一下空间复杂度。最后的值很大,记得用long long 并且每次计算都对1e9+7取模。由于数据很大,程序需要跑很长时间(实测c++需要80s左右),不过是填空题影响不大。
代码:
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int mod = 1e9+7;
ll arr[2][130000];
int main(){
int n, m;
cin >> n >> m;
arr[0][1] = 1;
for(int i = 2; i <= n; i++){
for(int j = 1; j <= i; j++){
arr[1][j] = (arr[0][j-1] + arr[0][j] ) % mod;
}
for(int j = 1; j <= i; j++){
arr[0][j] = arr[1][j];
}
}
cout << arr[1][m];
}
二、可以利用杨辉三角的另一个性质解决,杨辉三角第n行m个数的值其实就是 C n − 1 m − 1 C_{n-1}^{m-1} Cn−1m−1。因此求出 C 123455 123452 C_{123455}^{123452} C123455123452 的值即为答案,但直接求最后的数字很大,除法取模涉及到逆元相关的知识。可以将 C 123455 123452 C_{123455}^{123452} C123455123452 等价变成 C 123455 3 C_{123455}^{3} C1234553 通过编程或者计算器直接求出答案。
代码:
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int mod = 1e9+7;
int main(){
// 12345ll是定义为long long类型,防止计算溢出
cout << 123455ll*123454*123453 / 6 % mod;
}
三、还能通过其他方式求出答案,比如找规律、利用逆元等。杨辉三角有很多优美的性质,感兴趣的同学多了解一下。
4.sjm的棒棒糖
题目:
有一天sjm来到一个糖果店,糖果店的老板看sjm长的非常可爱,于是就跟sjm说,我给你两个数,一个是1136,一个1080, 你可以每次可以从中选一个大于 1 的数减 1,最后两个数都会减到 1,在减的过程中,如果两个数互质我就会给你一根棒棒糖。可是sjm对于质数很容易犯迷糊,请你帮帮sjm,找出她最多能得到多少根棒棒糖,并把最大数量输出出来。
答案:
2313
题解:
这题考察的知识点是动态规划,用使用记忆化递归也可以写,这里给出动态规划答案的代码与解析。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1500 + 7;
//f[i][j]代表着i 和 j 减到两个1互质的次数
int dp[maxn][maxn];
//求最大公因数
int gcd(int x,int y) {
return y == 0 ? x : gcd(y,x % y);
}
//动态规划预处理,求出前1500的答案
void init() {
for(int i = 1;i <= 1500;i++) {
for(int j = 1;j <= 1500;j++) {
//如果两个数 i 和 j 当前互质 那么他们减到1时互质的次数就等于
// num1 = (i - 1) 和 j 减到减到1时互质的次数 + 1
// num2 = i 和 (j - 1) 减到减到1时互质的次数 + 1
//这两者中的较大值
//i和j不互质则 num1 = (i - 1) 和 j 减到减到1时互质的次数 num2类似
int num1 = dp[i - 1][j] + (gcd(i,j) == 1);
int num2 = dp[i][j - 1] + (gcd(i,j) == 1);
dp[i][j] = max(num1,num2);
}
}
}
int main() {
init();
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",dp[a][b]);
return 0;
}
5. Beabled不相信完型填空有套路
题目
Beabled(以下简称小B)时长仰望星空的时候思考人生,静谧无人的今夜也不例外。小B回想起了高中的往事,英语是小B擅长的学科,其中一个原因是小B很喜欢高中的英语老师。有一次考前英语老师说完型填空有一个套路,每篇完型填空的答案都是A、B、C、D各五个,小B很信任老师,所以记下了这个套路。
小B在使用这个套路考试一年后,才想起要验证一下这个套路的正确性,于是他找到了至今为止考过的所有完型填空的答案,把这些答案拼成一个字符串,统计一下每个选项出现的个数,然后验证这个套路是否正确。如果每个选项出现的个数相等,则视为这个套路是正确的,输出“Beabled like English teacher the most!”;反之错误,则输出“Beabled don’t believe that cloze has a routine!”。
输入格式
输入一行字符串S代表出现过的所有选项。
输出格式
每个选项出现的数量相同,输出“Beabled最喜欢英语老师啦”。
不相同,输出“Beabled不相信完形填空有套路!”。
题解
本场签到题,使用字符串或者char数组读入后,遍历一遍统计A、B、C、D的个数,最后判断数量是否都一样即可。
代码
#include <iostream>
using namespace std;
string s;
int main()
{
cin >> s;
int a = 0, b = 0, c = 0, d = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] == 'A') a++;
else if (s[i] == 'B') b++;
else if (s[i] == 'C') c++;
else d++;
}
if (a == b && b == c && c == d) printf("Beabled like English teacher the most!");
else printf("Beabled don't believe that cloze has a routine!");
return 0;
}
6.四六级考试要来了
题目
马上就要四六级考试了,小T决定在接下来 n 天内好好背背单词,于是小T列了一个详细的计划表,写下了第 i 天要背的单词个数 ai ,但是小T是个大忙人,总会有各种事情影响小T的计划,于是小T根据自己的日程安排更改计划,比如第 i 到 j 天,少背 x 个单词,第 k 到 l 天,多背 y 个单词。但是小T改计划太多次了,希望你能够根据修改记录,计算出他的单词计划。
输入格式
第一行有两个整数 n,k,代表天数与更改计划的次数。
第二行有 n 个数,第i个数则代表最初计划的第i天所背的单词数。
接下来 k 行,每行有三个数,i, j, x 代表给第i天到第j天内,单词计划改变x个。
1 <= i <= j <= n, -1e6 <= x <= 1e6。
输出格式
输出仅一行,代表修改计划后,每天所背的单词数,用空格隔开
输入样例
3 2
1 2 3
1 2 -1
1 3 2
输出样例
2 3 5
数据范围
数据范围:
对于百分之60的数据 0 <= n, k <= 1000
对于百分之100的数据 0 <= n, k <= 1e5
题解
本题为经典的差分算法模板题,
小T每一次修改都是对某个连续区间内的元素进行相同的更改,如果仅仅将目标修改的区间遍历一遍,逐个更改,时间复杂度将会达到O(n^2)的级别,显然是无法通过全部数据点的。所以要使用差分算法,对某个区间进行修改的操作优化为O(1)。
需要注意的是本题x的数值范围为1e6,n,k的范围是1e5,在极端的数据情况下最大数据范围为1e11,使用int会产生溢出,需要使用long long。
当然使用树状数组或者线段树实现区间修改、查询也能够通过本题,不过有点大材小用的感觉了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
long long q[N];
int n, m, r, l, c;
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i=1;i<=n;i++){
int t;cin >> t;
//构建差分数组
q[i+1] -= t; q[i] += t;
}
while(m--){
cin >> l >> r >> c;
//将l-r区间内都加上c
q[r+1] -= c;
q[l] += c;
}
for(int i=1;i<=n;i++){
//前缀和运算
q[i] += q[i-1];
cout << q[i];
if(i != n)cout << " ";
}
return 0;
}
7.干饭人
题目
餐厅每到中午下课,人就会超级多。同学们为了可以早点买到饭,在下课后都会马上冲到餐厅去买饭。
已知某班有 n 名同学,学号为1,2,……,n。一到下课,所有同学都同时往餐厅内的m个窗口跑去,餐厅窗口用1,2,……,m编号。第 i 号同学的跑步速度为 vi ,目标窗口数为 mi假设今天餐厅只为此班同学开放,如果有同学同时跑到一个窗口,则以学号小的学生站在前面。现在你根据已知信息,输出每个窗口所排的队列
输入格式
第一行为两个整数 n , m ,表示 n 名学生, m 个窗口
下面有 n 行,第 i 行包含3个整数 a , b ,表示第 i 个同学的跑步速度和目标要去的餐厅窗口。
输出格式
输出 n 行,第 i 行表示第 i 个窗口所排的队列,学号之间用空格隔开
(如果没有同学前往此窗口,则输出 0 )
输入样例
5 3
10 8
2 1
3 2
4 1
3 2
1 2
输出样例
3 1
2 4 5
0
题解
本题所考察的算法为排序算法和结构体、c++STL的应用
首先观察本题数据范围, 0 <= n <= 2e6, 若是选择O(n^2)(如冒泡排序)或者O(nlogn)(如快速排序)的排序算法,则一定会有部分数据点出现超时的情况。
所以需要选择时间复杂度为O(n)的算法,本次通过桶排序的思想,通过空间换时间来解决此题目,需要将速度和学号按照一定的规则排序,可以发现题目中输入数据是按照学号从小到大输入的,因此学号是有序的,只需对速度进行排序,窗口数量<=10,0<速度<=1000,所以只需使用一个vector arr[15][1005]数组按照窗口和速度来存储数据,里面的的数据无需排序即为有序的;
冒泡排序代码(只能过百分之40的数据):
#include<bits/stdc++.h>
#define fi first
#define se second
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
typedef long long ll;
const int maxn = 1e6+10;
vector<pair<int, int>> arr[maxn];
int main(){
io;
int n, m, a, b;
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a >> b;
arr[b].push_back({a,i});
}
for(int i = 1; i <= m; i++){
if(arr[i].size() == 0){
cout << 0 << '\n';
}else{
for(int j = 0; j < arr[i].size(); j++){
// 冒泡排序
for(int k = 0; k < arr[i].size()-j-1; k++)
if(arr[i][k].fi < arr[i][k+1].fi ){
pair<int, int> t = arr[i][k];
arr[i][k] = arr[i][k+1];
arr[i][k+1] = t;
}
}
for(int j = 0; j < arr[i].size(); j++){
if(j !=0 ) cout << ' ';
cout << arr[i][j].se;
}cout << '\n';
}
}
}
快速排序代码(只能过百分之80的数据)
#include<bits/stdc++.h>
#define fi first
#define se second
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
typedef long long ll;
//const int maxn = 5e6+10;
vector<pair<int, int>> arr[15];
// sort的排序函数
bool cmp(pair<int, int> a, pair<int, int> b){
if(a.fi == b.fi){
return a.se < b.se;
}
return a.fi > b.fi;
}
int main(){
io;
int n, m, a, b;
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a >> b;
arr[b].push_back({a,i});
}
for(int i = 1; i <= m; i++){
if(arr[i].size() == 0){
cout << 0 << '\n';
}else{
sort(arr[i].begin(), arr[i].end(), cmp);
for(int j = 0; j < arr[i].size(); j++){
if(j != 0) cout << ' ';
cout << arr[i][j].se;
}cout << '\n';
}
}
}
桶排序代码(正解)
#include<bits/stdc++.h>
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
typedef long long ll;
vector<int> arr[15][1005];
int main(){
io;
int n, m, a, b;
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a >> b;
arr[b][a].push_back(i);
}
for(int i = 1; i <= m; i++){
int s = 0;
for(int j = 1000; j >= 1; j--){
for(int k = 0; k < arr[i][j].size(); k++){
if(s == 1) cout << ' ';
cout << arr[i][j][k];
s = 1;
}
}
if(s == 0) cout << 0;
cout << '\n';
}
}
8.跟djh一起数数的日子
djh有一天发现了一道题,想了很久却一直做不对。题意为:给定两个整数 a 和 b,求 a 和 b之间的所有数字中 0∼9 的出现次数。
例如,a=1024,b=1032,则 a 和 b 之间共有 9 个数如下:
1024 1025 1026 1027 1028 1029 1030 1031 1032
其中 0
出现 10 次,1
出现 10 次,2
出现 7次,3
出现 3次等等…
输入格式
输入包含多组测试数据。
每组测试数据占一行,包含两个整数 a 和 b。
当读入一行为 0 时,表示输入终止,且该行不作处理。
输出格式
每组数据输出一个结果,每个结果占一行。
每个结果包含十个用空格隔开的数字,第一个数字表示 0
出现的次数,第二个数字表示 1
出现的次数,以此类推。
数据范围
对于百分之10的数据 -10^3 < a , b < 10^3
对于百分之50的数据 -10^6 < a , b < 10^6
对于百分之70的数据 -10^9 < a , b < 10^9
对于百分之100的数据 -10^14 < a , b < 10^14
题解
本次暴力枚举O(n)的时间复杂度可以过百分之50的数据,正解是通过寻找数字中单个数字出现次数的规律来得出正确答案。可以通过规律求得1-n中单个数字k出现的次数(具体看代码注释),此外数据有负数,需要分类讨论一下。
代码
暴力代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll arr[15];
void cnt(ll n){
if(n == 0) arr[0]++;
while(n){
arr[n%10]++;
n = n/10;
}
}
int main(){
ll a, b;
cin >> a >> b;
ll ans = 0;
for(int i = a; i <= b; i++){
cnt(abs(i));
}
for(int i = 0; i <= 9; i++){
cout << arr[i];
if(i != 9) cout << ' ';
}
}
正解
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
LL a, b;
//统计这个数字有几位
int dgt(LL x)
{
int res = 1;
while(x)
{
x /= 10;
res++;
}
return res;
}
//统计1到n之间 i这个数字一共出现几次
LL cnt(LL n, int i)
{
//d为n的位数
int d = dgt(n);
//res为i一共出现几次
LL res = 0;
//从右边第一位也就是个位开始找,看这位置上i一共出现了几次
for(int j = 1; j <= d; j++)
{
//如果j是个位,p就是1,是十位,p就是10
//l为j位左边部分的数值 也就是下面的abc
//r为j位右边部分的数值
//dj为改数字在j位上的值
//比如1234567
//如果我看的是j = 4位也就是千位上,那么p= 1000,l=123,r=567,dj=4
LL p = pow(10, j - 1), l = n / p / 10, r = n % p, dj = (n / p) % 10;
//如果不是找0,那么计算第j位左边的整数小于l (xxx = 000 ~ abc - 1)的情况
if(i) res += l * p;
//如果是0,那么计算第j位左边的整数从1开始小于l (xxx = 001 ~ abc - 1)的情况
if( !i && l) res += (l - 1) * p;
//如果第j位比他大,那么加上 (xx = 000 ~ 999 )的情况
if(dj > i && (i || l)) res += p;
// 计算第j位左边的整数等于l (xxx = abc)的情况
if(dj == i && (i || l)) res += (r + 1);
}
return res;
}
int main()
{
cin >> a > b;
//判断是否都小于0,如果是则把他们转化为两个正数
if( a <= 0 && b <= 0)
{
a = -a;
b = -b;
swap(a, b);
}
//找这个区间含几个i
for(int i = 0; i <= 9; i++)
{
//如果两个数都大于0
if( a >= 0 )
{
LL ans = cnt(b, i) - cnt(a - 1, i);
if(i == 0 && a == 0) ans ++;
cout << ans << " ";
}
//或者有一个小于0
else
{
LL ans = cnt(b, i) + cnt(-a, i);
if(i == 0) ans ++;
cout << ans <<" ";
}
}
return 0;
}
另一种正解
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
// 求0-n中数字k出现的次数
ll cnt(ll n, int k){
ll f = n;
ll c = 1, res = 0, rem = 0;
while(n){
if(n % 10 == k) res += n / 10 * c + rem + 1;
else res += (n / 10 + (n % 10 > k-1)) * c;
rem += n % 10 * c;
if(k == 0) res -= c;
c *= 10;
n /= 10;
}
if(k == 0) res += 1;
return res;
}
int main(){
ll a, b;
cin >> a >> b;
for(int i = 0; i <= 9; i++){
ll ans = 0;
if(a >= 0 && b >= 0 || a <= 0 && b <= 0){
if(a >= 0){
ans += cnt( abs(b), i );
if(a != 0) ans -= cnt( abs(a-1), i );
}else{
ans += cnt(abs(a), i );
if(b != 0) ans -= cnt(abs(b+1), i );
}
}else{
ans += cnt(0-a, i);
ans += cnt(b, i);
if(i == 0) ans--;
}
cout << ans;
if(i!=9) cout << ' ';
}
}
9. Beabled的奇妙梦中旅程
Beabled(简称小B)喜欢睡觉,有时候一睡就是一整天,因此小B因睡觉耽误了好多事而受责怪。有一次,小B下午有一个重要的会议,但是小B现在很困,所以小B决定午睡一会,但是很不巧,做梦的时候小B发现自己被困在梦里出不去了,小B非常慌忙,但是却无能为力。这时有一个士兵过来告诉小B,说他有办法让小B逃脱梦境,士兵说:
附近有n个城堡,小B所处的位置就是1号古堡,每个古堡之间原本有互通的道路,但是因为政权问题,部分道路被阻断,现只存在部分连通的道路,而且每条连通的道路长度也不尽相同。在第n个城堡里,有女巫可以制造摆脱梦境的解药,你去要找到这个女巫,才有机会摆脱梦境。
小B听完后很感动,正准备动身前往时,士兵把小B拦下,说:
别着急,我还没告诉你古堡的分布呢,你现在去怎么能找到?我将会告诉你有m条连通两个古堡的道路,以及每条道路的路径长度l,剩下的就交给你了。
小B深知是场梦,听完还是很感动!在筹备好行动路线后就以最短的路径前往第n个古堡了。
最终小B成功逃脱了梦境,赶上了重要的会议。
那么聪明的你一定也能算出小B前往第n个古堡最短的路径是多少吧?
输入格式
第一行包含整数n和m。
接下来m行每行包含三个整数a,b,c,表示存在一条从点a到b的有向边,边长为c。
输出格式
输出一个整数,表示小B从1号古堡到n号古堡的最短距离。
若路径不存在,则输出“小B is Game Over!”。
数据范围
对于百分之20的数据n,m<=50,
对于百分之40的数据,n<=500,m<=1000,
对于百分之70的数据,n<=5000,m<=10000,
对于百分之100的数据,n,m<=150000。
题解
本题主要考察最短路径算法,是一个标准的模板题,但也可以通过奇形怪状的暴力骗取部分的分数。
使用floyd算法可以通过百分之40的数据,使用朴素dijkstra可以通过百分之70的样例。正解为堆优化版的dijkstra。感兴趣的可以在网上搜索一下相关知识,有很多相关的博客。
这里给出堆优化版的dijkstra解析。
堆优化版的dijkstra是对朴素版dijkstra进行了优化,在朴素版dijkstra中时间复杂度最高的寻找距离最短的点O(n^2)可以使用最小堆优化。
- 一号点的距离初始化为零,其他点初始化成无穷大。
- 将一号点放入堆中。
- 不断循环,直到堆空。每一次循环中执行的操作为:
弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定)。
用该点更新临界点的距离,若更新成功就加入到堆中。
时间复杂度分析
寻找路径最短的点:O(n)
加入集合S:O(n)
更新距离:O(mlogn)
代码
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 150010;
// 稀疏图用邻接表来存
int h[N], e[N], ne[N], idx;
int w[N]; // 用来存权重
int dist[N];
bool st[N]; // 如果为true说明这个点的最短路径已经确定
int n, m;
void add(int x, int y, int c)
{
w[idx] = c; // 有重边也不要紧,假设1->2有权重为2和3的边,再遍历到点1的时候2号点的距离会更新两次放入堆中
e[idx] = y; // 这样堆中会有很多冗余的点,但是在弹出的时候还是会弹出最小值2+x(x为之前确定的最短路径),并
ne[idx] = h[x]; // 标记st为true,所以下一次弹出3+x会continue不会向下执行。
h[x] = idx++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap; // 定义一个小根堆
// 这里heap中为什么要存pair呢,首先小根堆是根据距离来排的,所以有一个变量要是距离,其次在从堆中拿出来的时
// 候要知道知道这个点是哪个点,不然怎么更新邻接点呢?所以第二个变量要存点。
heap.push({ 0, 1 }); // 这个顺序不能倒,pair排序时是先根据first,再根据second,这里显然要根据距离排序
while(heap.size())
{
PII k = heap.top(); // 取不在集合S中距离最短的点
heap.pop();
int ver = k.second, distance = k.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i]; // i只是个下标,e中在存的是i这个下标对应的点。
if(dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({ dist[j], j });
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
memset(h, -1, sizeof(h));
scanf("%d%d", &n, &m);
while (m--)
{
int x, y, c;
scanf("%d%d%d", &x, &y, &c);
add(x, y, c);
}
cout << dijkstra() << endl;
return 0;
}
java版
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.PriorityQueue;
public class Main{
static int N = 150010;
static int n;
static int[] h = new int[N];
static int[] e = new int[N];
static int[] ne = new int[N];
static int[] w = new int[N];
static int idx = 0;
static int[] dist = new int[N];// 存储1号点到每个点的最短距离
static boolean[] st = new boolean[N];
static int INF = 0x3f3f3f3f;//设置无穷大
public static void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++;
}
// 求1号点到n号点的最短路,如果不存在则返回-1
public static int dijkstra()
{
//维护当前未在st中标记过且离源点最近的点
PriorityQueue<PIIs> queue = new PriorityQueue<PIIs>();
Arrays.fill(dist, INF);
dist[1] = 0;
queue.add(new PIIs(0,1));
while(!queue.isEmpty())
{
//1、找到当前未在s中出现过且离源点最近的点
PIIs p = queue.poll();
int t = p.getSecond();
int distance = p.getFirst();
if(st[t]) continue;
//2、将该点进行标记
st[t] = true;
//3、用t更新其他点的距离
for(int i = h[t];i != -1;i = ne[i])
{
int j = e[i];
if(dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
queue.add(new PIIs(dist[j],j));
}
}
}
if(dist[n] == INF) return -1;
return dist[n];
}
public static void main(String[] args) throws IOException{
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String[] str1 = reader.readLine().split(" ");
n = Integer.parseInt(str1[0]);
int m = Integer.parseInt(str1[1]);
Arrays.fill(h, -1);
while(m -- > 0)
{
String[] str2 = reader.readLine().split(" ");
int a = Integer.parseInt(str2[0]);
int b = Integer.parseInt(str2[1]);
int c = Integer.parseInt(str2[2]);
add(a,b,c);
}
System.out.println(dijkstra());
}
}
class PIIs implements Comparable<PIIs>{
private int first;//距离值
private int second;//点编号
public int getFirst()
{
return this.first;
}
public int getSecond()
{
return this.second;
}
public PIIs(int first,int second)
{
this.first = first;
this.second = second;
}
@Override
public int compareTo(PIIs o) {
// TODO 自动生成的方法存根
return Integer.compare(first, o.first);
}
}
10.夺宝游戏
题目描述
小H在玩一个游戏,游戏的规则是这样的:有一块3*n个格子组成的区域,即有3行n列,每一块格子上都有一个价值为v的宝物,游戏开始前小H在这块区域的起点:左上角(1,1)处,他需要要走到这块区域的终点:右下角(3,n)处。
他每次只能向右或者向下移动一格,不能向左或者向上移动。每走过一块格子就会将这块格子的宝物收入囊中(小H出生在(1,1)处,所以(1,1)处的宝物可以直接获得),到达终点后,小H拥有的宝物价值必须大于等于K,才能赢得游戏。请问小H有多少种走法能最终赢得游戏。
输入描述:
第一行两个正整数n, k。0 <= k <= 1e10。
接下来三行,每行n个整数,第i行第j个整数vij表示该块格子宝物的价值。0<=v<=1e4。
输出描述:
小H赢得游戏的不同方法数。
输入样例:
3 10
1 1 1
2 2 2
3 3 3
输出样例:
4
数据范围:
对于百分之10的数据n<=20,
对于百分之40的数据n<=5000,
对于百分之70的数据n<=50000,
对于百分之100的数据n<=500000。
题解
本题是这场比赛中相对比较难的一道题,但部分数据很水,依旧可以使用暴力骗取一些分数。
由于图只有3行,且不能往上走,因此小H只有两次向下走的机会。通过预处理前缀和做优化,然后枚举两次往下走的下标, 通过sum1[i]+sum2[j]−sum2[i−1]+sum3[n]−sum3[j−1]≥k 计算符合条件的数目,时间复杂度为O(n^2),可以通过百分之40的数据。
正解需要用到前缀和、树状数组(线段树)、离散化来解决。
首先预处理出每行的前缀和数组,接着注意到行数只有三,于是我们用 i 表示从第一行下到第二行的下标,用 j表示从第二行下到第三行的下标,那么问题可以转化成,求满足 sum1[i]+sum2[j]−sum2[i−1]+sum3[n]−sum3[j−1]≥k的 (i,j)对数。
我们对上式移项,变为 sum2[j]−sum3[j−1] ≥ k+sum2[i−1]−sum1[i]−sum3[n] (i<=j)。显然可以对其离散化后利用树状数组或者线段树进行求解。
时间复杂度: O(n×log(n))。
前缀和代码(通过百分之40数据)
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 5e5+10;
ll arr[4][maxn];
ll sum[4][maxn];
int main(){
ll n, k;
cin >> n >> k;
for(int i = 1; i <= 3; i++){
for(int j = 1; j <= n; j++){
cin >> arr[i][j];
sum[i][j] = sum[i][j-1] + arr[i][j];
}
}
ll ans = 0;
for(int i = 1; i <= n; i++){
for(int j = i; j <= n; j++){
if(sum[1][i] + sum[2][j]- sum[2][i-1] + sum[3][n] - sum[3][j-1] >= k) ans++;
}
}
cout << ans;
}
正解代码
#include<bits/stdc++.h>
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
typedef long long ll;
const int maxn = 1e6+10;
const int mod = 1e9+7;
ll bit[maxn*4], n, k;
void add(int p, ll x){
while(p <= n) bit[p] += x, p += p & -p;
}
ll ask(ll p){
ll res = 0;
while(p) res += bit[p], p -= p & -p;
return res;
}
ll range_ask(ll l, ll r){
return ask(r) - ask(l - 1);
}
vector<ll> alls;
int find(ll x) {
int l = 0, r = alls.size();
while (l < r){
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
}
ll arr[4][maxn];
ll sum[4][maxn];
ll s2[maxn];
int main(){
io;
ll ans = 0;
cin >> n >> k;
for(int i = 1; i<=3; i++){
for(int j = 1; j <= n; j++){
cin >> arr[i][j];
sum[i][j] = sum[i][j-1] + arr[i][j];
}
}
for(int i = 1; i <= n; i++){
s2[i] = sum[2][i]-sum[3][i-1];
alls.push_back(s2[i]);
}
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
for(int i = 1; i <= n; i++){
add(find(s2[i]), 1);
}
for(int i = 1; i <= n; i++){
ll sum1 = sum[1][i] - sum[2][i-1] + sum[3][n];
ans = (ans + range_ask(find(k-sum1),n) );
add(find(s2[i]), -1);
}
cout << ans;
}