Codeforces Round #555 (Div. 3) ABC1C2DEF 题解

// 为了给学弟学妹更好的理解,我尽量写详细一点。

A. Reachable Numbers

题意:设f(x)为 x+1 这个数去掉后缀0的数,现在给出n,问经过无数次这种变换后,最多能得到多少个不同的数。

思路:我们用一个数组标记用过的数x,然后开始循环变换x,直到x变成一个曾经用过的数就结束,那么问题来了,x最大可能有1e9,普通数组标记不了,这个时候我们可以用map来标记 x,stl 的 map原理是红黑树,我就不详细讲了,相关数据结构就是二叉查找树,你们可以了解下。

#include<bits/stdc++.h>
using namespace std;
map<int,int>mp;
int main()
{
	int x,res=0;
	cin>>x;
	while(!mp[x])
	{
		res++;
		mp[x]=1;
		x++;
		while(x%10==0)
		x/=10;
	}
	cout<<res;
}

B. Long Number

题意:给一个长度为n的字符串,给出 f 数组表示你可以把字符 x 变成对应的 f (x),你只能修改一段连续的子串,求修改后字典序最大的字符串。什么是字典序大家可以百度了解一下,我不详细讲。

思路:字典序最大,那么我们肯定优先从左边开始枚举,我们找到第一个 si < f(si) 的位置 p ,那么显然从这里开始修改 si 肯定是字典序最小的,我们一直修改直到 sj > f(sj)就结束。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
char s[maxn];
int vis[maxn];
int main()
{
	int n;
	cin>>n>>s+1;
	for(int i=1;i<=9;i++)
	cin>>vis[i];
	for(int i=1;i<=n;i++)
	s[i]-='0';
	for(int i=1;i<=n;i++)
	if(s[i]<vis[s[i]])
	{
		int j=i;
		while(j<=n)
		{
			if(s[j]<=vis[s[j]])
			s[j]=vis[s[j]];
			else
			break;	
			j++;		
		}
		break;
	}
	for(int i=1;i<=n;i++)
	printf("%d",s[i]);
}

C1. Increasing Subsequence (easy version)

题意:给一个长度为 n 的数组,每个数不一样,每次你可以从坐边界或者右边界取一个数,要求每一次取的数都要比上一次取得数要大,求最多可以取多少个数。

思路:我们用两个指针 p , q 分别表示当前的左边界,右边界,我们记一下上一次取得数为 x(初始化为0),假设当前左边界 <右边界,显然优先选左边界更优,但是我们因为 x 的存在,需要特判,如果 x < ap,那么就取ap,否则我们看 aq 是否大于 x,如果大于,那么就取aq,否则就结束了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int a[maxn],cnt;
char s[maxn];
int main()
{
	int n;
	scanf("%d",&n);
	int p=1,q=n,x=0;
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	while(p<=q)
	{
		if(a[p]<a[q])
		{
			if(a[p]>x)
			{
				s[++cnt]='L';
				x=a[p++];
			}
			else if(a[q]>x)
			{
				s[++cnt]='R';
				x=a[q--];
			}
			else break;
		}
		else
		{
			if(a[q]>x)
			{
				s[++cnt]='R';
				x=a[q--];
			}
			else if(a[p]>x)
			{
				s[++cnt]='L';
				x=a[p++];
			}
			else break;
		}
	}
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;i++)
	printf("%c",s[i]);
}

C2. Increasing Subsequence (hard version)

题意:同上,不过新加条件就是有可能有多个数是一样的(即有可能 ai = aj)。

思路:同上,不过假设当前我们的左右边界如果相等了,比如 2 3 4 5 4 2,假设我们已经取了 ans 个数,这个时候我们看一下从左边这个2最多能取多长,我们发现最多能取4个数,右边这个2显然只能取2个数,那么答案就是ans+4。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int a[maxn],cnt;
char s[maxn];
int main()
{
	int n;
	scanf("%d",&n);
	int p=1,q=n,x=0;
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	while(p<=q)
	{
		if(a[p]<a[q])
		{
			if(a[p]>x)
			{
				s[++cnt]='L';
				x=a[p++];
			}
			else if(a[q]>x)
			{
				s[++cnt]='R';
				x=a[q--];
			}
			else break;
		}
		else if(a[p]>a[q])
		{
			if(a[q]>x)
			{
				s[++cnt]='R';
				x=a[q--];
			}
			else if(a[p]>x)
			{
				s[++cnt]='L';
				x=a[p++];
			}
			else break;
		}
		else
		{
			if(a[p]<=x)break;
			int t1=1,t2=1;
			for(int i=p+1;i<q;i++)
			if(a[i]>a[i-1])t1++;
			else break;
			for(int i=q-1;i>p;i--)
			if(a[i]>a[i+1])t2++;
			else break;
			if(t1>=t2)
			{
				for(int i=1;i<=t1;i++)
				s[++cnt]='L';
			}
			else
			{
				for(int i=1;i<=t2;i++)
				s[++cnt]='R';
			}
			break;
		}
	}
	printf("%d\n",cnt);
	for(int i=1;i<=cnt;i++)
	printf("%c",s[i]);
}

D. N Problems During K Days

题意:你要设计一个长度为 k 的数组a,a1 你可以设置为任何数,要求 ai>ai-1 且 ai<=2*ai-1,a数组的和要刚好等于n。

思路:我们先二分确定好起点,假设我们设置起点为 x,那么这个数组最小的和就是x+(x+1)+(x+2)...也就是等差数列求和,最大的和就是等比数列求和,我们先二分找一个最小的x,使得等比数列求和 >= n,然后再判断等差数列求和是否 <=n,如果 >n 显然无解,有过有解,那我们就开始输出 x,然后 k-=1,n-=x,然后接下来继续二分找一个比刚刚的 x 大一点的 x即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+10;
int a[maxn];
ll n,k;
int ok(ll x)
{
	ll res = 1<<min(30,(int)k);
	res--;
	if(n>res*x)return 0;
	return 1;	
}
void dfs(int x)
{
	if(k==1)
	{
		printf("%lld\n",n);
		return;
	}
	printf("%d ",x);
	n-=x;
	k--;
	int l=x+1,r=x*2;
	while(l<r)
	{
		int m=(l+r)/2;
		if(ok(m))
		r=m;
		else
		l=m+1; 
	}
	dfs(l);
}
int main()
{
	cin>>n>>k;
	int l=1,r=n;
	while(l<r)
	{
		int m=(l+r)/2;
		if(ok(m))
		r=m;
		else
		l=m+1;
	}
	if(1ll*k*(l*2+k-1)/2>n)puts("NO");
	else
	{
		puts("YES");
		dfs(l);
	}
}

E. Minimum Array

思路:给你一个a数组,一个b数组,定义ci = (ai + bi)%n,你可以重新排列b数组,使得ci字典序最小,我们先把 bi 存起来(可以用multiset,我用的线段树),枚举 ai,令 x = (n-ai)%n,然后我们先在[x, n-1]的区间(这个区间指的是权值不是下标)找一个最小的没用过的bj,(用数据结构实现,multi 或者线段树,复杂度logn),如果没找到,那我们继续在[ 0 , x-1 ]这个区间继续找最小的没用过的bj 即可,然后标记bj,答案就是 (ai+bj)%n。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int mx[maxn*4],mn[maxn*4],inf=1e9;
int a[maxn],b[maxn];
void up(int o,int l,int r,int k,int v)
{
	if(l==r)
	{
		mn[o]=v;
		return;
	}
	int m=(l+r)/2,ls=o*2,rs=o*2+1;
	if(k<=m)up(ls,l,m,k,v);
	else up(rs,m+1,r,k,v);
	mx[o]=max(mx[ls],mx[rs]);
	mn[o]=min(mn[ls],mn[rs]);
}
int qmin(int o,int l,int r,int ql,int qr)
{
	if(l>=ql&&r<=qr)return mn[o];
	int m=(l+r)/2,ls=o*2,rs=o*2+1,res=1e9;
	if(ql<=m)res=min(res,qmin(ls,l,m,ql,qr));
	if(qr>m)res=min(res,qmin(rs,m+1,r,ql,qr));
	return res;
}
int main()
{
	int n,x;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	scanf("%d",&a[i]);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&x);
		up(1,0,n-1,x,x);
		b[x]++;
	}
	for(int i=0;i<n;i++)
	if(!b[i])up(1,0,n-1,i,inf);
	for(int i=0;i<n;i++)
	{
		x=(n-a[i])%n;
		int ans,p;
		int t1=qmin(1,0,n-1,x,n-1);
		int t2=qmin(1,0,n-1,0,x);
		if(t1==inf)
		ans=a[i]+t2,p=t2;
		else
		ans=a[i]+t1,p=t1;
		printf("%d ",ans%n);
		b[p]--;
		if(!b[p])
		up(1,0,n-1,p,inf);
	}
}

F. Maximum Balanced Circle

题意:给你一个长度为 n 的 a 数组,你可以从中选择尽量多的元素,重新排列,使得相邻两个数相差不超过1,首尾元素也相差不超过1。

思路:仔细分析一下我们发现,假设有这样的数 1 2 2 3 3 3 4 5 5,那么答案肯定就是7,也就是我们按顺序(每次+1的顺序),找到第一个不为0的数(出现次数不为0),然后再继续找到第一个出现次数<=1的数,那么这段区间的所有数都是能组成合法的数组的,然后我们和答案去max一下就好了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+10,N=2e5;
int a[maxn],b[maxn];
int main()
{
	int n,x;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>x;
		a[x]++;
	}
	if(n==1)
	{
		printf("%d\n%d",1,n);
		return 0;
	}
	int ans=1;
	int p1,p2,t1,t2;
	int p=1;
	while(p<=N)
	{
		while(!a[p]&&p<=N)
		p++;
		
		p1=p;
		int res=a[p];
		p++;
		while(a[p]>1&&p<=N)
		res+=a[p],p++;
		
		res+=a[p];
		p2=p;
		if(res>ans)
		{
			ans=res;
			t1=p1,t2=p2;
		}
	}
	printf("%d\n",ans);
	p1=1,p2=1;
	for(int i=t1;i<=t2;i++)
	for(int j=1;j<=a[i];j++)
	{
		if(j==1)
		{
			b[p1]=i;
			p1--;
			if(p1<1)p1+=ans;			
		}
		else
		{
			++p2;
			b[p2]=i;	
		}	
	}
	for(int i=1;i<=ans;i++)
	printf("%d ",b[i]);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值