Codeforces Round #753 (Div. 3)题解

目录

A. Linear Keyboard

 B. Odd Grasshopper

C. Minimum Extraction

D. Blue-Red Permutation

E. Robot on the Board 1

F. Robot on the Board 2


A. Linear Keyboard

给你一个包含26个键的键盘,每个键对应一个小写拉丁字母。你需要在键盘上打出一个单词,单词由小写拉丁字母组成。为了打印出该单词你需要移动到对应的按键上,花费的时间是相邻按键在键盘上的位置差(取绝对值)。

Input:

第一行的整数,表示测试用例的数量,随后两行是测试用例的描述;

第一行是长度为26的a~z组成的小写字符串keyboard。

第二行是需要打出的单词s。

Output:

输出在给出的键盘上打出单词s需要花费的最小时间。(实际上顺序打出单词没有什么最小时间)

题解:

思路比较简单,算出每个字母的位置存下来,然后遍历一遍累加差值即可。

代码1:

使用STL,string.find()可以查找字符在字符串中的位置。

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
 
int main()
{
    int cnt;
    cin>>cnt;
    while(cnt--){
        string keyboard, answer;
        cin>>keyboard>>answer;
        vector<int> pos;
        for(const auto& x: answer){
            pos.push_back(keyboard.find(x));
        }
        int dis = 0;
        for(size_t i = 1; i < pos.size() ; ++i){
            dis += abs(pos[i]-pos[i-1]);
        }
        cout<<dis<<endl;
    }
    return 0;
}

代码2:

使用数组映射字符对应的位置

#include "bits/stdc++.h"
using namespace std;
int main(){
	int32_t t;
	cin >> t;
	string s, s1;
	int32_t a[26];
	for (int32_t i = 0; i < t; i++) {
		cin >> s;
		for (int32_t j = 0; j < 26; j++) a[s[j] - 'a'] = j;
		cin >> s1;
		int32_t ans = 0;
		for (int32_t j = 1; j < s1.size(); j++) ans+= abs(a[s1[j - 1] -'a'] - a[s1[j] - 'a']);
		cout << ans << endl;
}

 B. Odd Grasshopper

        有一个蝗虫在x坐标轴上的x0位置上。它可以向左跳d步到x0-d,也可以向右跳到x0+d的位置上。跳跃的距离从1开始递增,跳跃的方向是由它所在的位置决定,如果坐标是偶数就向左跳,奇数向右跳。

Input       

输入起始坐标x0,跳跃次数n。

Output

跳跃n次后,蝗虫的坐标

题解:

手动模拟了一下从奇数和偶数的坐标出发,跳跃4次后会回到原点。n如果是4的倍数,最后还是回到原点。如果不是,模拟蝗虫从x0出发,跳跃n%4次的结果。

代码1:

#include <iostream>
 
using namespace std;
using ll = long long;
ll jpm(ll x0, ll n, ll i){
    while(n--){
        if(x0%2 == 0){
            x0-=i;
        } else{
            x0+=i;
        }
        ++i;
    }
    return x0;
}
 
int main()
{
    int cnt;
    cin>>cnt;
    while(cnt--){
        ll x0,n;
        cin>>x0>>n;
        if(n%4 == 0){
            cout<<x0<<endl;
        } else{
            cout<<jpm(x0,n%4,(n/4)*4+1)<<endl;
        }
    }
    return 0;
}

代码2:

大神(博客主人)的超精简版代码,思路是一样的。

#include "bits/stdc++.h"
using namespace std;
int main(){
	int32_t t;
	cin >> t;
	while (t--) {
		int64_t a, b, c;
		cin >> a >> b;
		c = (b - b % 4);
		while (++c <= b) {
			if (a % 2 == 0) a -= c;
				else a+=c;
		}
		cout << a << endl;
	}	
}

C. Minimum Extraction

        你有包含n个整数的数组a,如果数组长度大于1,你要找到这个数组最小的数m,如果有多个可以选任一个,然后将m从这个数组中移除,剩下的每个元素减去m,这个操作叫minimum extraction。

        你想在这个操作过程中使数组的最小值尽可能大,为了达到这个目的,你可以进行任意次minimum extraction操作。

Input

数组长度和数组a

Output

minimum extraction后数组最大的最小值

题解:

先将代码排序,每次minimum extraction后数组的最小值就是a[i+1]-a[i],遍历一下找出a[i+1]-a[i]的最大值即可

代码1:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
 
int main()
{
    int cnt;
    cin>>cnt;
    while(cnt--){
        int n;
        cin>>n;
        vector<int> nums(n,0);
        for(int i = 0 ; i < n ; ++i){
            cin>>nums[i];
        }
        sort(nums.begin(),nums.end());
        int maxn = nums[0];
        for(int i = 0 ; i < n-1 ; ++i){
            maxn = max(maxn, nums[i+1]-nums[i]);
        }
        cout<<maxn<<endl;
    }
    return 0;
}

D. Blue-Red Permutation

        有长度为n的数组a,数组的元素可以相同,每个元素都被标记为red或blue,被标记为blue的元素每次只能减1,被标记为red的元素每次只能加1。有没有可能多次操作后,使数组成为1~n的序列。

Input:

数组长度n,数组a,由'R'和'B’组成的字符串,标记元素的颜色。

Output:

YES 或者NO,表示是否可以转换成1~n的序列

4
1 2 5 2
BRBR

题解:

先把数组重新排序,标记为B的放前面,R放后面,然后按照数字大小排序,排序后是这样

1 5 2 2
B B R R

第i个位置上的元素值为ai,如果被标记为B,如果ai小于i,因为只能递减就不可能转化为i,,R类似,如果ai大于i,只能递增也不可能转化为i。

代码1:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
 
int main()
{
    int cnt;
    cin>>cnt;
    while(cnt--){
        int n;
        cin>>n;
        vector<int> num(n,0);
        string color;
        for(int i = 0 ; i < n ; ++i){
            cin>>num[i];
        }
        cin>>color;
        vector<int> blue;
        vector<int> red;
        for(int i = 0 ; i < n ; ++i){
            if(color[i] == 'B'){
                blue.push_back(num[i]);
            } else {
                red.push_back(num[i]);
 
            }
        }
        sort(blue.begin(),blue.end());
        sort(red.begin(),red.end());
        bool b = true;
        for(int i = 0 ; i < blue.size() ; ++i){
            if(blue[i]<=i){
                    b = false;
                    break;
            }
        }
        bool r = true;
        for(int i = 0,j = n-red.size()+1 ; i < red.size() ; ++i,++j){
            if(red[i] > j){
                    r = false;
                    break;
            }
        }
        if(b && r){
            cout<<"Yes"<<endl;
        } else{
            cout<<"No"<<endl;
        }
    }
    return 0;
}

大神代码:

#include "bits/stdc++.h"
using namespace std;
int32_t a[200010];
string s;
int main(){
	int32_t t, n;
	scanf("%d", &t);
	while (t--) {
		vector<int32_t> l, r;
		scanf("%d", &n);
		for (int32_t i = 0; i < n; i++) {
			scanf("%d", &a[i]);
		}
		cin >> s;
		for (int32_t i = 0; i < n; i++) {
			if (s[i] == 'B') l.push_back(a[i]);
				else r.push_back(a[i]);
		}
		sort(l.begin(), l.end());
		sort(r.begin(), r.end(), greater<int32_t>());
		bool flag = true;
		for (int i = 0; i < l.size(); i++) 
			if (l[i] < i + 1) flag = false;
		for (int i = 0; i < r.size(); i++) {
			if (r[i] > n - i) flag = false;
		}
		if (!flag) {
			printf("NO\n");
		} else {
			printf("YES\n");
		}
	}	
}

E. Robot on the Board 1

        有一个机器人在一个n x m的棋盘上,行数从上到下从1到n进行编号,列数从左到右从1到n进行编号。这个机器人可以从一个格子移动到另一个相邻的格子。我们会给机器人一串命令由 'L', 'R', 'D','U'组成,代表着可以向左、右、下、上移动。

        机器人可以从任一个格子出发,开始按顺序执行命令,如果机器人移动超过棋盘的边界,就结束,如果一个命令导致机器人出界,就认为执行失败。

        为了使机器人可以执行尽可能多的命令不掉下去,我们要选取一个起始位置。

Input

输入n,m表示棋盘的高度和宽度,然后是由'L', 'R', 'D', 'U组成的字符串表示将要执行的命令。

Output

输出能成功执行最多命令的起始位置,如果有多个可以选取任意一个。

题解:

假设从原点出发,模拟执行命令,记录每次命令执行后的x,y坐标,并记录最左、最右、最上和最下的点,如果最左到最右、最上到最下的距离超出了范围就失败,否则就调整起点位置。

代码:
 

#include <bits/stdc++.h>
 
using namespace std;
 
int main()
{
    int cnt;
    cin>>cnt;
    while(cnt--){
        int m,n;
        cin>>m>>n;
        string command;
        cin>>command;
        int x0,y0;
        x0 = y0 = 1;
        int right,left,up,down;
        right = left = up = down = 0;
        int x,y;
        x = y = 0;
        for(const auto & c:command){
            switch(c){
            case 'R':
                y++;break;
            case 'L':
                y--;break;
            case 'U':
                x--;break;
            case 'D':
                x++;break;
            }
            right = max(right,x);
            left = min(left,x);
            down= max(down,y);
            up = min(up,y);
            if(abs(right-left) >= m || abs(up-down) >= n){
                break;
            }
            x0 = 1 - left;
            y0 = 1 - up;
        }
        cout<<x0<<" "<<y0<<endl;
    }
    return 0;
}

F. Robot on the Board 2

题意和E题差别在于给出机器人在每个格子上都一个固定的行走方向,增加一个条件如果机器人走到之前访问过的位置也失败,

输入和E题相同

输出可以能成功执行最多命令的起始位置和移动的最大的步数

题解:

感谢cxj提供本题思路。一个简单暴力的做法,只需要从每一个节点出发,dfs到不能走的地方为止,记录能走多远。时间复杂度上限是棋盘大小的平方。

以这个思路作为基础,两个优化点:

1、从一个节点出发到停止为止的路径上的每一个点,作为初始节点出发到达的终点都是在相同的边界,或者在一个环上。都可以一次性算出这些点到达终点能走的步数,要么是到达边界的总步数,要么是环的大小。

2、从一个点到达一个前面已经计算过的点时,那么就进入了之前遍历过的路径,不需要重复更新。

注意的坑点:因为需要处理环,所以用一个数组记录到达过的点。

因为可能会出现栈溢出,所以我必须用循环代替递归改写了dfs才通过此题。。。但是cxj却递归过了,不知道这是什么魔法。

具体实现如下

非递归代码(xt):

#include "bits/stdc++.h"
using namespace std;
char c[2010][2010];
int32_t h[2010][2010];
int32_t t, n, m;
int32_t flagx,  flagy, sizeC;
int main(){
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d", &n, &m);
		for (int32_t i = 0; i < n; i++) {
			scanf("%s", c[i]);
		}
		for (int32_t i = 0; i < n; i++) {
			for (int32_t j = 0; j < m; j++) {
				h[i][j] = -1;
			}
		}
		for (int32_t i = 0; i < n; i++) {
			for (int32_t j = 0; j < m; j++) {
				if (h[i][j] >= 0) continue;
				flagx = flagy = -1;
				set<pair<int, int> > s;
				vector<pair<int, int> >st;
				int x = i;
				int y = j;
				while (true) {
					st.push_back(make_pair(x, y));
					s.insert(make_pair(x, y));
					int32_t xx = x;
					int32_t yy = y;
					//printf("%d %d\n", x, y);
					if (c[x][y] == 'D') xx++;
					if (c[x][y] == 'U') xx--;
					if (c[x][y] == 'R') yy++;
					if (c[x][y] == 'L') yy--;
					if (xx < 0 || xx >= n || yy < 0 || yy >= m) {
						h[x][y] = 0;
						break;
					}
					if (h[xx][yy] != -1) {
						h[x][y] = h[xx][yy] + 1;
						break;
					}
					if (s.find(make_pair(xx, yy)) != s.end()) {
						h[x][y] = 0;
						flagx = xx;
						flagy = yy;
						break;
					}
					x = xx;
					y = yy;
				}			
				int pflag = -1;
				for (int32_t i = st.size() - 2; i >= 0; i--) {
					h[st[i].first][st[i].second] = h[st[i + 1].first][st[i + 1].second] + 1;
					if (flagx == st[i].first && flagy == st[i].second) {
						pflag = i;
					}
				}
				for (int32_t i = pflag; i < st.size(); i++) {
					h[st[i].first][st[i].second] = h[flagx][flagy];
				}
			}
		}
		int32_t ansx = 0; 
		int32_t ansy= 0;
		for (int32_t i = 0; i < n; i++) {
			for (int32_t j = 0; j < m; j++) {
				if (h[ansx][ansy] < h[i][j]) {
					ansx = i;
					ansy = j;
				}
			}
		}
		printf("%d %d %d\n", ansx + 1, ansy + 1, h[ansx][ansy] + 1);
	}
}

递归代码(cxj):

#include <bits/stdc++.h>
#define x first
#define y second
//#define int long long
using namespace std; 
typedef pair<int,int> PII;
const int N=2005;
int n,m;
int dist[N][N];
char g[N][N];
bool st[N][N];
PII p[N*N];
int cnt;
 
int dfs(int x,int y){
	p[++cnt]={x,y};
	if(x<1||x>n||y<1||y>m||st[x][y]) return dist[x][y];
	
	st[x][y]=true;
	if(g[x][y]=='U') dist[x][y]=dfs(x-1,y)+1;
	if(g[x][y]=='D') dist[x][y]=dfs(x+1,y)+1;
	if(g[x][y]=='L') dist[x][y]=dfs(x,y-1)+1;
	if(g[x][y]=='R') dist[x][y]=dfs(x,y+1)+1;
	
	return dist[x][y];
}
 
void solve(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	cin>>g[i][j];
	
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		if(!st[i][j]){
			cnt=0;
			dfs(i,j);
			
			//如果有环,要更新整个环的dist 
			PII t=p[cnt];
			for(int k=1;k<cnt;k++){
				if(p[k]==t){
					for(int h=k;h<=cnt;h++)
					dist[p[h].x][p[h].y]=cnt-k;//这里的长度应该是整个环的长度 
				}
			}
		}
	}	
	
	int x=1,y=1,res=dist[x][y];
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		if(dist[i][j]>res){
			x=i,y=j,res=dist[i][j];
		}
	}
	
	cout<<x<<" "<<y<<" "<<res<<'\n';
	
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	st[i][j]=false,dist[i][j]=0;
 
}
 
int main(){
	ios_base::sync_with_stdio(false);
	int T;
	cin>>T;
	while(T--)solve();
}

G. Banquet Preparations 1

        你有两个整数数组a1...an与b1...bn,你需要对于每一个1<=i<=n, 从ai与bi中一共减掉总和为m的整数,使得\left | \sum_{i = 1}^{n}ai - \sum_{i = 1}^{n}bi \right |尽量小

输入:

        t组数据,每组包含一个n和m,以及n行的ai 与 bi。

输出:

        t组数据,每组包含最小的所求的值,然后n行,每行包含ai和bi的减小大小。

题解:

        感谢cxj提供本题思路。

        对于一组ai与bi,假设ai减小a,那么bi减小m-a,那么变化后的\left | ai - bi \right |就等于\left | ai - a - (bi - m + a) \right | = \left | ai - 2a - bi + m) \right | = \left | ai - bi + (m - 2a) \right |,其中ai - bi + m是定值,只需要关注2a的范围即可。

那么同理,如果所有的ai一共扣去了A,那么所有的bi一共扣去了nm - A,那么我们所要求的最小值为:\left | \sum_{i = 1}^{n}ai - \sum_{i = 1}^{n}bi + nm - 2A\right |,式子中只有A为变量,其余都是定值,因此只需要得到A的取值范围区间,根据该表达式的分段函数,判断其值。

我们可以确定每一组ai、bi可以提供的最小ai的减少量以及最大减少量,通过累加得到A的最小值与最大值。

代码实现如下:

#include "bits/stdc++.h"
using namespace std;
const int32_t MAXN = 200000 + 10;
long long totala, totalb;
long long a[MAXN], b[MAXN], ansA[MAXN], ansB[MAXN];
int main(){
	long long n, m, t;
	scanf("%lld", &t);
	while (t--) {
		scanf("%lld%lld", &n, &m);
		totala = totalb = 0;
		long long mina = 0, minb = 0;
		for (int32_t i = 0; i < n; i++) {
			scanf("%lld%lld", &a[i], &b[i]);
			ansA[i] = a[i];
			ansB[i] = b[i];
			totala += a[i];
			totalb += b[i];
			if (b[i] < m) {
				mina += (m - b[i]);
			}
			if (a[i] < m) {
				minb += (m - a[i]);
			}
		}
		long long constT = totala - totalb + 1LL * n * m;
		long long optA = 0;
		if (2 * mina > constT) optA = mina;
		if (2 * mina <= constT && constT <= 2 * (n * m - minb)) optA = constT / 2;
		if (constT > 2 * (n * m - minb)) optA = n * m - minb;
		for (int32_t i = 0; i < n; i++) {
			if (ansB[i] >= m) {
				ansB[i] -= m;
			} else {
				ansB[i] = 0;
				ansA[i] -= (m - b[i]);
			}
		}
		printf("%lld\n", abs(constT - 2 * optA));
		optA -= mina;
		for (int32_t i = 0; i < n; i++) {
			long long tmp = min(ansA[i], b[i] - ansB[i]);
			if (tmp < optA) {
				optA -= tmp;
				ansA[i] = ansA[i] - tmp;
				ansB[i] = ansB[i] + tmp;
			} else {
				ansA[i] -= optA;
				ansB[i] += optA;
				optA = 0;
			}
			printf("%lld %lld\n", a[i] - ansA[i], b[i] - ansB[i]);
		}	
	}
}

H. Banquet Preparations 2        

        题意与G不同之处,每一组ai,bi的m值都可能不同,为mi。且最终需要让最终不同的(ai, bi)对数尽量少。

题解:

        因为ai + bi - mi不同的组,一定得到的(ai, bi)不同,我们只需要对ai + bi - mi相同的作为一个集合,集合里得到的(ai, bi)尽量少。

        那么我们可以通过ai, bi, mi得到每一组的(ai,bi)的可能取值范围,只要确定了ai的最大值最小值即可确定bi范围。这个的处理与上一题做法是相同的

        (ai, bi, mi)与(aj, bj, mi)可以减完后变成同一组,当且仅当ai的取值范围与aj的取值范围有交点或重叠。

        我们对于每组的ai取值范围作为区间,排序,贪心的取尽量靠后的点作为ai的取值,用尽可能少的点,使得所有的区间都被ai覆盖。即为最终不同的(ai,bi)组数。

代码:

#include "bits/stdc++.h"
using namespace std;
const int32_t MAXN = 200000 + 10;
struct food{
	int32_t total;
	int32_t l, r, a, b, m;
	int32_t no;
} f[MAXN];
 
bool cmp(food a, food b) 
{
	if (a.total != b.total) {
		return a.total < b.total;
	} else {
		return a.l < b.l;
	}
}
 
int32_t countA(food f[], int32_t n)
{
	int32_t j = 0;
	int32_t minr = 0;
	int32_t ans = 0;
	for (int32_t i = 0; i < n; i = j) {
		minr = f[i].r;
                j = i + 1;
		while (j < n && f[j].l <= minr) {
			minr = min(f[j].r, minr);
			j++;
		}
		for (int k = i; k < j; k++) {
			f[k].l = minr;
		}
		ans++;
	}
	return ans;
}
 
int32_t cmp1(food a, food b)
{
	return a.no < b.no;
}
 
int main(){
	int32_t n, t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d", &n);
		for (int32_t i = 0; i < n; i++) {
			scanf("%d%d%d", &f[i].a, &f[i].b, &f[i].m);
			f[i].total = f[i].a + f[i].b - f[i].m;
			if (f[i].a > f[i].m) {
				f[i].l = f[i].a - f[i].m;
			} else {
				f[i].l = 0;
			}
			if (f[i].b > f[i].m) {
				f[i].r = f[i].a;
			} else {
				f[i].r = f[i].a - (f[i].m - f[i].b);
			}
			f[i].no = i;
		}
		sort(f, f + n, cmp);
		int32_t r = 0;
		int32_t ans = 0;
		for (int32_t l = 0; l < n; l = r) {
                        r = l + 1;             
			while (r < n && (r == 0 || f[r].total == f[r - 1].total)) {
				r++;
				continue;
			}
      			ans += countA(f + l, r - l);
		}
		printf("%d\n", ans);
		sort(f, f + n, cmp1);
		for (int32_t i = 0; i < n; i++) {
			printf("%d %d\n", f[i].a - f[i].l, f[i].b - (f[i].a + f[i].b - f[i].m - f[i].l));
		}
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值