2023河南萌新联赛第(一)场:河南农业大学【部分题解C、E、F、G、H、J、K】

C 硬币游戏

题目地址:https://ac.nowcoder.com/acm/contest/61132/C

题目描述

Alice和Bob想到了一种硬币游戏。在他们面前有一排硬币,每个硬币有正面和反面两种状态,规定正面为0反面为1。现在Alice和Bob想通过轮流操作使所有的硬币都处于同一种状态。一个人在操作结束后,如果已经处于这种局面,那么他就赢得游戏。每次操作可以选取连续不超过k个硬币,选取的硬币必须同为正面或者同为反面,比如“111”, “00”,”1”,但是不能不选硬币。选完之后会将硬币反转并放回原来的位置。两个人都会采取最优策略,并且Alice选择先手。如果Alice获胜,请输出“Alice“,Bob获胜则输出”Bob“,如果谁都无法获胜,请输出 ” 😦 “ 。

输入输出

第一行输入两个整数n, k,分别表示硬币个数和每次最多拿取的硬币个数。
接下来输入一行字符串,只包含‘0’,‘1’两种字符,字符串大小为n。
输出"Alice"or"Bob"。

数据范围

1≤n≤1000000
0<k≤n

输入样例

4 2
1100

输出样例

Alice

思路

这道题是一个博弈游戏,题目大致意思就是给一个字符串,然后两人轮流操作,每次操作可以选择一个连续且相同的字符串,字符串长度不能超过k,然后将此段字符串翻转。如果谁操作后字符串全为0或1时,那么他将获胜。
这个博弈有三种结果:1、Alice获胜;2、Bob获胜;3、平局。

先来判断Alice获胜的情况,Alice是先手,当他首先执行一次操作后,如果他没有获胜的话,那么Bob只需在重复一次Alice的操作,那么Alice将无法获胜。这时我们就得出了Alice的获胜情况,即:如果Alice能一次操作直接获胜,那么他必胜,否则将不会获胜。

再来判断Bob获胜的情况,Bob是后手,当Alice第一次操作后没有获胜,到Bob开始。此时Bob就会遇到和Alice同样的处境,即:如果当前不能直接获胜,那么将无法获胜。因为第三步Alice重复Bob的操作,那么Bob将无法获胜。

那么最后平局的情况就显而易见了,当前两步无法确定出胜负的话,那么必定是平局。

C++实现

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
// #include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
#define ll long long
#define ull unsigned long long 
#define endl '\n'

typedef pair<char, int> pir;
const int mod = 0x7f7f7f7f;
const int N = 100010;

int x0, x1, y0, y1;
bool flag;

int main(void){
    IOS
	
	int n, k; cin >> n >> k;
	string s; cin >> s;
	
	 if(s[0] == '0') {
	 	flag = false; x0 ++ ;
	 } else {
	 	flag = true; x1 ++ ;
	 }
	 
	 for(int i = 1; i < s.size(); i ++ ) {		//计算连续相同子字符串的长度及个数
	 	if(s[i] == '0' && !flag) {
	 		x0 ++ ;
	 		if(x0 > k) {
	 			x0 -= k; y0 ++ ;
			 }
		 } else if(s[i] == '0' && flag) {
		 	y1 ++ ;
		 	x1 = 0;
		 	x0 ++ ;
		 	flag = false;
		 } else if(s[i] == '1' && flag) {
		 	x1 ++ ;
		 	if(x1 > k) {						//若长度大于k,它就相当于2个及以上的字符串,不能当作一个,因为无法一次翻转
		 		x1 -= k; y1 ++ ;
			 }
		 } else if(s[i] == '1' && !flag) {
		 	flag = true;
		 	y0 ++ ;
		 	x0 = 0;
		 	x1 ++ ;
		 }
	 }
	 if(x0) y0 ++ ;
	 if(x1) y1 ++ ;
	 
	 if(y0 == 0 || y1 == 0) {
	 	if(n > k) cout << "Bob" << endl;
	 	else cout << "Alice" << endl;
	 } else if(y0 == 1 || y1 == 1) {
	 	cout << "Alice" << endl;
	 } else if(s == "0101" || s == "1010" || s == "1100" || s == "0110" || s == "0011" || s == "1001") {
	 	cout << "Bob" << endl;
	 } else {
	 	cout << ":(" << endl;
	 }
	
    return 0;
}

E 动物朋友

题目地址:https://ac.nowcoder.com/acm/contest/61132/E

题目描述

已知有n个动物朋友排成一排,每个动物朋友都有一个正整数的快乐值,涛涛每次会和连续的动物朋友玩,并且获得这些动物朋友快乐值的和的快乐,而涛涛是个完美主义者,他觉得快乐值刚好是m时候才是快乐的,现在请问有多少种选择方式,使得所选的连续的动物朋友的快乐值刚好为m。

输入输出

第一行输入n和m。

第二行输入n个正整数,第i个代表第i个动物朋友的快乐值。
输出一个整数,表示可能存在的选法数量,如果没有,就输出0;

数据范围

1<=n<=1e6
1<=m<=1e6
1<=ai<=1e5

输入样例

11 45
1 4 1 9 19 8 10 8 1 2 3

输出样例

1

思路

这道题是求区间和为m的个数。输出个数,没有则输出0。
因此我们可以使用一个队列储存区间和。首先遍历数组,当队列的区间和小于m时,将当前遍历到的元素添加到队列里,然后将队列里的和更新,然后判断和是否为m,是则ans++;若和大于m,则依次删除队头元素,直至和小于等于m。最后输出ans即可。

C++实现

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
#define ll long long
#define ull unsigned long long 
#define endl '\n'

typedef pair<char, int> pir;
const int mod = 0x7f7f7f7f;
const int N = 1e6 + 10;

int a[N], b[N], ans;
queue<int> q;

int main(void){
    IOS
	
	int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i ++ ) {
        cin >> a[i];
    }
    int sum = 0;
    for(int i = 1; i <= n + 1; i ++ ) {
        while(q.size() && sum > m) {
            sum -= a[q.front()];
            q.pop();
        }
        if(sum == m) ans ++;
        q.push(i);
        sum += a[i];
    }
    cout << ans << endl;
    return 0;
}

F 松鼠排序

题目地址:https://ac.nowcoder.com/acm/contest/61132/F

题目描述

松鼠宝宝有一排n个大小不一的坚果,松鼠宝宝想把坚果从小到大排序,每次他会选择两个坚果a和b每次花费1点力气把这两个坚果交换,爱动脑筋的松鼠宝宝想知道他排完这n个坚果一共需要花费的最少力气是多少?

输入输出

第一行一个整数n代表坚果数
接下来一行n个整数代表每个坚果的大小(每个坚果大小都不一样,即大小为1-n的一个排列)
一行输出代表松鼠宝宝花费的最小力气。

数据范围

1<=n<=1e5
1<=x<=n

输入样例

3
3 2 1

输出样例

1

思路

这道题就是将一组乱序的数字升序排列,但是每次只能选中两个数字并交换位置,求最少交换次数。
这道题想要求最少交换次数,那么我们就要知道按照什么规则进行排序才能使交换次数最少。
容易想到每次交换与升序排列后位置不相同的数字,若该位置升序排列后为x,则应将该位置的数字与x替换,这样能使得交换次数最少。
实现过程中,每次将当前位置错误的数字与正确数字相替换,同时将被替换的数字下标改为新位置的下标。

C++实现

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
#define ll long long
#define ull unsigned long long 
#define endl '\n'

typedef pair<int, int> pir;
const int mod = 0x7f7f7f7f;
const int N = 1e5 + 10;

int ans, b[N];
pir a[N];

int main(void){
    IOS
	
	int n; cin >> n;
    for(int i = 1; i <= n; i ++ ) {
        cin >> a[i].first;
        a[i].second = i;
        b[i] = a[i].first;
    }
    sort(a + 1, a + n + 1);
    for(int i = 1; i <= n; i ++ ) {
        if(b[i] != a[i].first) {
            ans ++;
            b[a[i].second] = b[i];
            a[b[i]].second = a[i].second;
            b[i] = a[i].first;
        }
    }
    cout << ans << endl;
    return 0;
}

G Reverse

题目地址:https://ac.nowcoder.com/acm/contest/61132/G

题目描述

给定一个长度为n的01串,你需要选择一段任意长度(可以为0)的区间对其翻转,翻转后,求最长的一段连续的全是1的区间的长度。

输入输出

链接:https://ac.nowcoder.com/acm/contest/61132/G
来源:牛客网

输入共2行。
第一行一个整数n
第二行一个长度为n的01序列。
输出一个整数,表示最长的长度。

数据范围

1 <= n <= 1e6

输入样例

10
0111001011

输出样例

5

样例说明

翻转区间[5,10],翻转为0111110100。

思路

字符串中任意两个不相邻子序列可以通过上述的翻转方式相连。
然后本题所求就变成:求字符串中前两长的子1串长度相加。
因此只需求出所有子1串的长度,然后排序,将最大的两个相加即可。
但是字符串中可能只有0,这需要特判一下。

C++实现

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
#define ll long long
#define ull unsigned long long 
#define endl '\n'

typedef pair<char, int> pir;
const int mod = 0x7f7f7f7f;
const int N = 1e6 + 10;

int a[N];

int main(void){
    IOS
	
	int n; cin >> n;
    string s; cin >> s;
    int tot = 0;
    for(int i = 0; i < n; i ++ ) {
        int sum = 0;
        while(s[i] == '1') {
            sum ++;
            i ++;
        }
        a[++tot] = sum;
    }
    sort(a + 1, a + tot + 1);
//     for(int i = 1; i <= tot; i ++ ) cout << a[i] << " ";
    int ans = 0;
    if(tot == 0) ans = 0;
    else ans = a[tot] + a[tot - 1];
    cout << ans << endl;
    
	
    return 0;
}

H 迷宫探险

题目地址:https://ac.nowcoder.com/acm/contest/61132/H

题目描述

在与boss的最终决战之后,小蓝来到了冒险的最后一关,在他面前有一个nm的迷宫,迷宫中道路用’.’表示,墙壁则由‘#’表示。小蓝初始在[1,1]的位置,他只有到达[n,m]才能开启最终的宝藏。小蓝现在迫不及待的想要开启宝藏,所以他想最短的时间内走出迷宫。现在迷宫内有一种特殊的装置 –“弹射器”。弹射器的格子用’’表示。当走到有弹射器的一格时,小蓝必须选择一个方向,弹射器会让他沿着这个方向弹射 x个距离,不同弹射器的弹射距离可以不同。弹射后的格子如果超过迷宫边界或者是墙壁则不能选择这个方向。小蓝现在可以向上下左右四个方向走,每走一个格子需要消耗一个单位时间,弹射则不消耗时间。求最短需要多少时间小蓝才能走出迷宫。如果无法到达终点,输出-1。

弹射器的数量,位置和弹射距离将在输入中给出。起点和终点一定不是弹射器。

输入输出

第一行两个整数 n, m,接下来n行,每行m个只包含 ’ . ’ , ’ * ’ , ’ # ’ 的字符描绘迷宫。

接下来一行一个整数k,下面的k行每行三个整数x, y, w表示在[x,y]格子的弹射器能弹射的距离。

输出一行一个整数。

数据范围

(2≤n≤3000,2≤m≤3000, n*m≤500000, 0≤k, w在int范围内)

输入样例

3 2
.*
#.
..
1
1 2 2

输出样例

1

思路

这道题可以用bfs来写,但是搜索过程不好处理。然后通过题目描述可以发现这道题是求最短路径,所以我们可以使用求最短路算法dijkstra来写。
使用dijkstra来写的好处是可以保持局部最优解,即保持我们储存的从一个点到另一个点的距离始终最短,然后通过局部最优达到全局最优即最短路径。
要达到保存两点间的最短路径,同时去除两点间的重复路径,容易想到使用STL里面具有去重排序的set集合容器,将点的坐标(x, y) 和距离起点的距离放入集合内部。
在代码具体实现过程中,按照dijkstra的思想,先判断当前位置是否为弹射器,然后按照规则判断四个方向,并更新新的坐标与起点的距离。要注意当通过弹射器移动时不加步数。然后按照正常移动判断前后左右,并更新新的点的位置。

C++实现

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
#define ll long long
#define ull unsigned long long 
#define endl '\n'
#define int long long

typedef pair<int, int> pir;
const int mod = 0x7f7f7f7f;
const int inf = 1e18;
const int N = 3005;

char s[N][N];
int b1[N][N], b2[N][N];
int nex[4] = {1, -1, 0, 0};
int ney[4] = {0, 0, 1, -1};

signed main(void){
    IOS
	int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i ++ ) {            //输入并初始化b1[]
        for(int j = 1; j <= m; j ++ ) {
            cin >> s[i][j];
            b1[i][j] = inf;
        }
    }
    int q1; cin >> q1;
    while(q1 -- ) {
        int x, y, z; cin >> x >> y >> z;
        b2[x][y] = z;
    }
    
    set<pair<int, pir>> q;
    q.insert({0, {1, 1}});
    b1[1][1] = 0;
    while(!q.empty()) {
        pair<int, pir> d = *q.begin();
        q.erase(q.begin());
        if(s[d.second.first][d.second.second] == '*') {   //判断一下当前位置为弹射器的情况
            for(int i = 0; i < 4; i ++ ) {                //枚举四个方向
                int x1 = d.second.first + nex[i] * b2[d.second.first][d.second.second];
                int y1 = d.second.second + ney[i] * b2[d.second.first][d.second.second];
                if(x1 >= 1 && x1 <= n && y1 >= 1 && y1 <= m && s[x1][y1] != '#') {
                    if(b1[x1][y1] > d.first) {
                        q.erase({b1[x1][y1], {x1, y1}});
                        b1[x1][y1] = d.first;
                        q.insert({b1[x1][y1], {x1, y1}});
                    }
                }
            }
            continue;
        }
        for(int i = 0; i < 4; i ++ ) {                    //枚举在当前位置往下一步走的情况
            int x1 = d.second.first + nex[i];
            int y1 = d.second.second + ney[i];
            if(x1 >= 1 && x1 <= n && y1 >= 1 && y1 <= m && s[x1][y1] != '#') {
                if(b1[x1][y1] > d.first + 1) {            //更新最短路径
                    q.erase({b1[x1][y1], {x1, y1}});
                    b1[x1][y1] = d.first + 1;
                    q.insert({b1[x1][y1], {x1, y1}});
                }
            }
        }
    }
    if(b1[n][m] == inf) cout << "-1" << endl;            //b1[n][m] = inf,说明从起点出发无法到达终点
    else cout << b1[n][m] << endl;
    
    return 0;
}

J 合唱比赛

题目地址:https://ac.nowcoder.com/acm/contest/61132/J

题目描述

河南农业大学信管学院举办一年一度的合唱比赛,目前你是评委之一,剩下还有其他的n位评委,给定一个正整数n和n个正整数表示这n个评委给目前在表演的团队的分数,评分规则为在所有评委(包括你)的分数中去掉一个最高分和最低分,剩下的取平均值(总共n-1个值),现在你可以参与评分(1~100之间的整数),问最终结果会在什么区间内,用两个数表示这个区间,结果保留6位小数。

输入输出

第一行给定一个正整数n

接下来一行给定n个整数表示n个评委的分数

输出两个保留六位的小数l,r表示答案。

数据范围

2<=n<=1000

输入样例

4
3 5 9 13
3
80 90 100

输出样例

5.666667 9.000000
85.000000 95.000000

思路

求数组平均值,保留6位小数。
当求最大平均值时,需要自己大的分要比当前最高分大;求最小平均值时,同理,需要自己的分要比最小数下。
题目要求去除最大最小值,则求最大平均值时只去除最小值,求最小平均值时之去除最小值。

C++实现

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
#define ll long long
#define ull unsigned long long 
// #define endl '\n'

typedef pair<char, int> pir;
const int mod = 0x7f7f7f7f;
const int N = 100010;

int a[N];

int main(void){
//     IOS
	
	int n; cin >> n;
    int sum = 0;
    for(int i = 1; i <= n; i ++ ) {
        cin >> a[i];
        sum += a[i];
    }
    sort(a + 1, a + n + 1);
    sum = sum - a[1] - a[n];
    int minn = sum + a[1];
    int maxx = sum + a[n];
    n--;
    printf("%.6lf %.6lf\n", (double)minn / n, (double)maxx / n);
	
    return 0;
}

K 以撒和隐藏房间

题目地址:https://ac.nowcoder.com/acm/contest/61132/K

题目描述

以撒又一次的逃进了地下室

地下室可以看作一个n*m的矩阵的迷宫,其中有些格子是有门相连房间,有些则是无法通过的墙壁。以撒发现其中一些墙壁似乎是空心的,可以通过爆炸打开隐藏的房间,而隐藏房的生成有一定的规律,以撒认为一个墙壁格子在满足以下所有情况时可能会是隐藏房间:

1, 该墙壁格子和三个普通房间相邻

2, 在满足1条件的情况下,不能和boss房间相邻

但是以撒正在和萌死戳交战,

现在你需要编写程序告诉他是否存在可能是隐藏房间的格子。

如果存在,输出两行,第一行是一个YES,第二行输出可能为隐藏房间的格子的数量

如果不存在,输出NO

输入输出

第一行两个整数n,m
然后是一个n*m矩阵,表示地图状态,0表示墙壁,1表示房间,2表示boss房间

如果存在,输出两行,第一行是一个YES,第二行输出可能为隐藏房间的格子的数量
如果不存在,输出NO

数据范围

3< m , n <= 1000

输入样例

3 3
001
110
211

输出样例

YES
1

思路

这道题直接爆搜就好了,n、m的值小于1000,然后每个点有四种移动方式,所以这道题的时间复杂度只有O(4nm),即1000 10004=4000000,1s时间很充足。

C++实现

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
#define ll long long
#define ull unsigned long long 
#define endl '\n'

typedef pair<char, int> pir;
const int mod = 0x7f7f7f7f;
const int N = 1005;

bool flag = false;
string s[N];
int ne[2][4] = {{0, 0, 1, -1}, {1, -1, 0, 0}};

int main(void){
    IOS
	
	int n, m; cin >> n >> m;
    for(int i = 0; i < n; i ++ ) {
        cin >> s[i];
    }
    int res = 0;
    for(int i = 0; i < n; i ++ ) {
        for(int j = 0; j < m; j ++ ) {
            if(s[i][j] == '1' || s[i][j] == '2') continue;
            int sum = 0;
            bool ff = true;
            for(int k = 0; k < 4; k ++ ) {
                int x = i + ne[0][k];
                int y = j + ne[1][k];
                if(x < 0 || x >= n || y < 0 || y >= m) continue;
                if(s[x][y] == '1') sum ++;
                if(s[x][y] == '2') {
                    ff = false; break;
                }
            }
            if(ff && sum == 3) {
                res ++;
//                 cout << i << j << endl;
            }
        }
    }
    
    if(res) {
        cout << "YES" << endl << res << endl;
    } else {
        cout << "NO" << endl;
    }
	
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值