Codeforces Round #753 (Div. 3)

A.Linear Keyboard

题目描述

​ 某键盘的 26 26 26个字母键排成一行,顺序是(长度为 26 26 26 的)字符串 s s s,现要打出字符串 t t t,需要在键盘上移动几个格子?例如在 a b … z ab\dots z abz 键盘上打出 a c e ace ace 需要移动 2 + 2 = 4 2+2=4 2+2=4 格。

分析

​ 记录每个字符的位置,对字符串 s s s。计算 ∑ i = 1 i < s . s i z e ( ) i d [ s [ i ] ] − i d [ s [ i − 1 ] ] \sum_{i=1}^{i<s.size()}id[s[i]]-id[s[i-1]] i=1i<s.size()id[s[i]]id[s[i1]]

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5+100;
int mp[maxn];
int n,m;

int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		string s;cin>>s;
		for (int i=0;i<s.size();++i)
			mp[s[i]] = i;
		cin>>s;int ans = 0;
		for (int i=1;i<s.size();++i)
		{
			ans += abs(mp[s[i]]-mp[s[i-1]]);
		}cout<<ans<<endl;
	}
}

B.Odd Grasshopper

题目描述

​ 数轴上,给一起始坐标点 x x x。在第 i i i秒,如果此时坐标为奇数则 x + = i x+=i x+=i,否则 x − = i x-=i x=i

问第 n n n秒末的位置。

分析

​ 可知在经历了 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4秒后,坐标 x x x停在了原地

即,从一个奇数秒 i i i开始: + i − ( i + 1 ) − ( i + 2 ) + ( i + 3 ) = 0 +i-(i+1)-(i+2)+(i+3) = 0 +i(i+1)(i+2)+(i+3)=0 不变

因此我们只需关注最后三秒即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;

int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		ll x,n;
		cin>>x>>n;
		for (ll i = n/4*4+1;i<=n;++i)
		{
			if (x%2)x += i;
			else x -= i;
		}
		cout<<x<<endl;
	}
}

C.Minimum Extraction

题目描述

​ 对于数组 a a a,可以进行以下操作任意次:删除最小的元素 m m m,然后将所有其他元素都减少 m m m

使得数组的最小元素最大

分析

​ 如果最小元素为一个负数,那么所有的数删除掉最小元素后都变大了。

也就是说,只要最小元素还是负数,且数组大小不为 1 1 1,那么我就一直删除

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+100;
ll a[maxn];
int n;

int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n;
		for (int i=1;i<=n;++i)cin>>a[i];
		sort(a+1,a+1+n);
		ll cur = 0;ll ans = a[1];
		for (int i=1;i<=n;++i)
		{
			a[i] = a[i] + cur;
			cur -= a[i];
			ans = max(ans, a[i]);
		}cout<<ans<<endl;
	}
}

D.Blue-Red Permutation

题目描述

​ 现有一数组,每个元素都有一种颜色(蓝色或红色)。

  • 若为蓝色,则你可以将其元素值减少任意一个正整数;
  • 若为红色,则你可以将其元素值增大任意一个正整数。

请问,若干次操作后,是否可以将数组变为 1 1 1 n n n 的一种排列。

分析

​ 贪心的想法。我们可以分别处理蓝色的数字和红色的数字

蓝色的数字,我们按照原数字从小到大排序后,优先填充构造 1 , 2 , 3 , … , n 1,2,3,\dots,n 1,2,3,,n

然后对于红色的数字我们按照数字从大到小排序后,优先填充构造 n , n − 1 , n − 2 , … , 1 n,n-1,n-2,\dots,1 n,n1,n2,,1

最终看是否可以全部填充完毕

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+100;
ll a[maxn];
int n;

int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n;
		for (int i=1;i<=n;++i)cin>>a[i];
		vector<int> v1,v2;
		for (int i=1;i<=n;++i)
		{
			char ch;cin>>ch;
			if (ch=='B')
			{
				if (a[i]<1)continue;
				if (a[i]>n)a[i]=n;
				v1.push_back(a[i]);
			}
			else
			{
				if (a[i]>n)continue;
				if (a[i]<1)a[i]=1;
				v2.push_back(a[i]);
			}
		}sort(v1.begin(),v1.end());
		sort(v2.begin(),v2.end());
		reverse(v2.begin(),v2.end());
		if (v1.size()+v2.size()!=n)
		{
			cout<<"NO\n";
			continue;
		}
		int l = 0;bool f = true;
		for (int i=0;i<v1.size();++i)
		{
			if (l+1<=v1[i])
			{
				++l;
			}else 
			{
				f = false;
				break;
			}
		}if (!f)
		{
			cout<<"NO\n";
			continue;
		}
		int r = n+1;
		for (int i=0;i<v2.size();++i)
		{
			if (r-1>=v2[i])--r;
			else
			{
				f = false;
				break;
			}
		}
		if (!f)
		{
			cout<<"NO\n";
			continue;
		}cout<<"YES\n";
	}
}

E.Robot on the Board 1

题目描述

​ 现有一串由 U , D , L , R U,D,L,R U,D,L,R 组成的指令,分别对应上、下、左、右的移动。机器人在 n × m n\times m n×m 大小的棋盘上执行命令,若超出棋盘,则命令执行失败,直接退出。

问:一开始应当将机器人摆在哪个位置,才能尽可能多地执行命令?

分析

​ 我们可以 x , y x,y x,y两个维度分开看。

L , R L,R L,R。我们找到一个 x 0 x_0 x0,使得能执行最多的命令

U , D U,D U,D。我们找到一个 y 0 y_0 y0,使得能执行最多的命令

考虑 x x x轴。刚开始初始位置的选取范围可以是 [ 1 , m ] [1,m] [1,m],随着命令数的增加,这个选取范围也在不断地变小

具体选取缩小范围的方法见代码

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+100;
int n,m;
 
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	for (int t=1;t<=T;++t)
	{
		string s;
		cin>>n>>m;cin>>s;
		int l = 1,r = m;
		int cnt = 0;
		for (int i=0;i<s.size();++i)
		{
			if (l==r)break;
			int pre = cnt;
			if (s[i]=='L')--cnt;
			else if (s[i]=='R')++cnt;
			else continue;
			while (cnt+l<=0)++l;
			while (cnt+r>m)--r;
		}
		int ll=1,rr = n;cnt=0;
		for (int i=0;i<s.size();++i)
		{
			if (ll==rr)break;
			int pre = cnt;
			if (s[i]=='U')--cnt;
			else if (s[i]=='D')++cnt;
			else continue;
			while (cnt+ll<=0)++ll;
			while (cnt+rr>n)--rr;
		}
		cout<<ll<<" "<<l<<endl;
	}
}

F.Robot on the Board 2

题目描述

​ 与 E E E不同的是,指令被写在格子上,每次到一个格子,就会执行上面的指令。若超出棋盘,则指令执行失败。问从哪个格子开始,能执行最多的指令?

分析

​ 可以看出,这类似于在一个有向图上求最长路。

但是这个图是有特点的!本图的原型是一个二维数组,这意味着每一个点的出度 ≤ 1 \le 1 1

这意味着每个环都是单独的出现,即我们绝对不可能从一个环抵达到另一个环。

那么,我们的最远的路径一定是从一个入度为 0 0 0的点到达一个环然后绕环一周 或者 一个点

因此,我们 d f s dfs dfs每一个点,计算 p s [ i ] [ j ] ps[i][j] ps[i][j]表示从点 ( i , j ) (i,j) (i,j)开始走能到达的最远的路

如果该点在一个环上的话,那么 p s [ i ] [ j ] ps[i][j] ps[i][j]就为环的长度

空间很紧

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 4e6+100;
int xmp[1000],ymp[1000];
char gra[2005][2005];
int vis[2005][2005];
int ps[2005][2005];
int id[2005][2005];
int tot=0;
int n,m;
inline bool check(int x,int y)
{
	return x>=1&&x<=n&&y>=1&&y<=m;
}
int dfs(int x,int y,int mk)
{
	if (vis[x][y]==mk)
	{
		ps[x][y] = tot+1-id[x][y];
		return ps[x][y];
	}id[x][y] = ++tot;
	if (vis[x][y]<mk&&vis[x][y]>0)return ps[x][y];
	vis[x][y]=mk;ps[x][y]=1;
	char ch = gra[x][y];
	int nx = x + xmp[ch]; int ny = y + ymp[ch];
	if (check(nx,ny))
	{
		dfs(nx,ny,mk);
		if (id[nx][ny]<=id[x][y])
		{
			ps[x][y] = ps[nx][ny];
			id[x][y] = id[nx][ny];
		}
		else ps[x][y] += ps[nx][ny];
	}return ps[x][y];
}
int main()
{
	ios::sync_with_stdio(0);
	ymp['R'] = 1;ymp['L'] = -1;
	xmp['U'] = -1;xmp['D'] = 1;
	int T;cin>>T;
	while (T--)
	{
		cin>>n>>m;for (int i=0;i<=n;++i)for (int j=0;j<=m;++j)vis[i][j] = ps[i][j] = 0;
		for (int i=1;i<=n;++i)for (int j=1;j<=m;++j)cin>>gra[i][j];
		int ansx = -1,ansy = -1,ans_len = 0;int mk =0;
		for (int x=1;x<=n;++x)for (int y=1;y<=m;++y)if (vis[x][y]==0)
		{
			tot = 0;
			int len = dfs(x,y,++mk);
			if (len>ans_len)
			{
				ans_len = len;
				ansx = x;
				ansy = y;
			}
		}
		cout<<ansx<<" "<<ansy<<" "<<ans_len<<"\n";
	}
}

G.Banquet Preparations 1

题目描述

​ 给定两个非负数组 a , b a, b a,b。以及一个非负整数m,保证 a i + b i ≥ m a_i + b_i ≥ m ai+bim

每个位置的两个数 a i , b i a_i,b_i ai,bi,需要减少 m m m。我们的目的是要使得平衡 ∣ ∑ a i − ∑ b i ∣ |\sum a_i - \sum b_i | aibi尽量小。

这个并且要求出使得平衡值最小时,每个 a i , b i a_i,b_i ai,bi分别要减少多少

分析

​ 重要的是一步转化

我们确定了,每一对都要减去 m m m。那么实际上最终 ∑ a i + b i \sum a_i+b_i ai+bi 也是确定了的,记为 t o t tot tot

∑ b i \sum b_i bi s u m sum sum (这里的 b i b_i bi为修改过后的 b i b_i bi

那么最终的答案就是 ∣ t o t − 2 ∗ s u m ∣ |tot - 2*sum| tot2sum

因此,我们只需考虑 s u m sum sum就可以了

s u m sum sum的目标最多两个:

  • t o t tot tot为奇数时, t o t / 2 tot/2 tot/2或者 t o t / 2 + 1 tot/2+1 tot/2+1
  • t o t tot tot为偶数时, t o t / 2 tot/2 tot/2

如何尽可能的优化 s u m sum sum呢?

首先对于每个 b i b_i bi记其最多可以减去 R [ i ] R[i] R[i],最少可以减去 L [ i ] L[i] L[i]

那么我们可以先减去 L [ i ] L[i] L[i],把此时的 s u m sum sum计算出来

然后,我们在根据现在的情况贪心的去减即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+100;
int a[maxn],b[maxn];
int L[maxn],R[maxn];int c[maxn];
int n,m;
ll sum,tot;
ll solve(ll tar)
{
	static ll S;
	S = sum;
	for (int i=1;i<=n;++i)
	{
		ll cap = R[i] - L[i];
		if (S<=tar)
		{
			c[i]=L[i];
		}
		else if (S-cap>=tar)
		{
			S-=cap;
			c[i] = L[i]+cap;
		}
		else
		{
			c[i] = L[i]+S-tar;
			S = tar;
		}
	}return abs(tot - 2*S);
}
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n>>m;tot=0;sum=0;
		for (int i=1;i<=n;++i)cin>>a[i]>>b[i];
		for (int i=1;i<=n;++i)
		{
			tot += a[i]+b[i]-m;
			L[i] = max(0,m-a[i]);
			R[i] = min(b[i],m);
		}
		for (int i=1;i<=n;++i)sum += b[i]-L[i];
		if (tot%2==0)
		{
			cout<<solve(tot>>1)<<endl;
			for (int i=1;i<=n;++i)
			{
				cout<<m-c[i]<<" "<<c[i]<<endl;
			}
		}
		else
		{
			ll ans = solve(tot>>1);
			ll res = solve((tot>>1)+1);
			if (ans<res)solve(tot>>1);
			cout<<min(ans,res)<<endl;
			for (int i=1;i<=n;++i)
			{
				cout<<m-c[i]<<" "<<c[i]<<endl;
			}
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值