Codeforces Round #714 (Div. 2)题解+补题

A.Array and Peaks:

题目链接:https://codeforces.ml/contest/1513/problem/A

题目大意:

有t组数据,每一组数据给定n,k,要求求出一个长度为n的排列,使得这个排列有k个峰,如果不能有k个峰则输出-1。峰的意义是,如果a[i]是峰,那么一定满足a[i]>a[i-1] && a[i]<a[i+1]
数据范围满足:1<=t<=100,1<=n<=100,0<=k<=n。

题目分析:

1.我们发现如果将峰数为0的排列:1,2,3,······,n-1,n。除了1以外,每两项进行一个交换,那么就会形成一个峰。例如我们选择4,5,则排列变成:1,2,3,5,4,······,n-1,n。这样就形成了一个峰。
2.我们发现,如果我们选择两两不同的数对进行交换,这样形成的峰是不会互相影响的。比如选择(2,3)和(4,5),则排列变成:1,3,2,5,4,6,······,n-1,n。这样就形成了两个独立的峰。所以只要我们选取除了1以外,x个两两不同的数对,那么就可以形成x个峰。
3.此时还有一个问题,那就是当题目要求形成k个峰可行时,以这样的方式形成形成的峰一定有k个吗?因为峰大于左右两边的值的原因,所以每三个数只能由1个峰。长度为n的排列,最多能有(n-1)/2个峰。而我们在1中生成峰的方法同样最多能生成(n-1)/2个峰。所以方法正确。

正解程序:

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>

using namespace std;
typedef long long ll;
const ll maxn=110;
ll num[maxn],T;
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		ll n,k,times=0,pos=1;
		scanf("%lld%lld",&n,&k);
		num[1]=1;
		for(ll i=2;i<=n;i+=2)
		{
			if(k==0)//等于0则不需要制造峰
				break;
			if(i+1<=n)//如果i已经为n了,则没有办法制造出峰
			{
				++times;
				num[i]=i+1;//交换相邻两项制造出峰
				num[i+1]=i;
				if(times==k)
				{
					pos=i+2;
					break;
				}
			}
		}
		for(ll i=pos;i<=n;i++)//将后续部分保持原样
			num[i]=i;
		if(times!=k)//如果没有制造出k个峰,输出-1
			printf("-1\n");
		else
		{
			for(ll i=1;i<=n;i++)
				printf("%lld ",num[i]);
			printf("\n");
		}
	}
	
	return 0;
}

B.AND Sequences:

题目链接:https://codeforces.ml/contest/1513/problem/B

题目大意:

有t组数据,每组数据给定一个长度为n的数组,问有多少种方式可以将这个数组重新排列之后满足:a1=a2&a3&······&an-1&an,a1&a2=a3&······&an-1&an,······,a1&a2&a3&······&an-1=an
数据范围满足:1<=t<=1e4,2<=n<=2e5,0<=ai<=1e9。

题目分析:

1.首先,我们挨个分析每一个式子:

  • a1=a2&a3&······&an-1&an,很容易知道a1=a2&a3&······&an-1&an=a1&a2&a3&······&an-1&an
  • a1&a2=a3&······&an-1&an,很容易知道a1&a2=a3&······&an-1&an=a1&a2&a3&······&an-1&an
  • a1&a2&a3&······&an-1=an,很容易知道a1&a2&a3&······=an-1&an=a1&a2&a3&······&an-1&an

2.所以很显然,要让这个一连串的式子成立,只有当a1&a2=a3&······&an-1&an=a1&a2&a3&······&an-1&an 等于a1和an时才能成立。而且,除了首尾两个数以外,其他的数具体处于什么位置并不重要。
3.所以我们首先算出last=a1&a2&a3&······&an-1&an,然后判断last是否在a1到an中出现过至少两次。则最终的答案就是 C t i m e s [ l a s t ] 2 C_{times[last]}^{2} Ctimes[last]2* A n − 2 n − 2 A_{n-2}^{n-2} An2n2

正解代码:

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>

using namespace std;
typedef long long ll;
const ll maxn=2e5+10;
const ll mod=1e9+7;
ll num[maxn],fac[maxn],T;
int main()
{
	scanf("%lld",&T);
	fac[0]=1;
	for(ll i=1;i<=maxn-10;i++)//计算阶乘,用于求出排列数
		fac[i]=fac[i-1]*i%mod;
	while(T--)
	{
		ll n,flag,last,times=0;
		scanf("%lld",&n);
		for(ll i=1;i<=n;i++)
		{
			scanf("%lld",&num[i]);
			if(i==1)//计算所有数的与
				last=num[i];
			else
				last&=num[i];
		}
		for(ll i=1;i<=n;i++)//判断last出现了多少次 
			if(last==num[i])
				times++;
		if(times>1)//如果last至少出现了2次
			printf("%lld\n",times*(times-1)%mod*fac[n-2]%mod);//计算答案
		else//否则不可能
			printf("0\n");
	}
	
	return 0;
}

C. Add One:

题目链接:https://codeforces.ml/contest/1513/problem/C

题目大意:

有t组数据,每组数据给定两个数n和m。定义一次操作为将n的每一位加1,如果某一位变成了10,则在后续的处理中当成两位来计算。例如,9进行两次操作即为21。现要求对n进行m次操作,询问m次操作之后,最后的数的长度为多少。
数据范围满足:2<=t<=2e5,1<=n<=1e9,1<=m<=2e5。

题目分析:

1.很显然,这个数n的每一位都是相互独立的,所以我们单独考虑每一位进行d次操作之后的长度,最后将每一位的答案加起来就是最终的答案。
2.如果将某一个数变成0~9中的任何一个都是不需要增加的,或者说最后的长度都是1。
3.所以考虑后续大于等于10的情况,我们将1每一次操作的变换结果打印出来,观察打表数据之后我们发现,第i次变换的结果是第i-9和第i-10次变换的拼接的结果。如图所示:

4.所以变换为i的长度就是变换为i-9和变换为i-10的长度之和,我们用递推的方式来进行这一过程即可。

正解代码:

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>

using namespace std;
typedef long long ll;
const ll maxn=3e5+10;
const ll mod=1e9+7;
ll dp[maxn],T;
int main()
{
	for(ll i=0;i<=9;i++)//变成0-9长度仍然是1 
		dp[i]=1;
	for(ll i=10;i<=maxn-10;i++)//打表所得的结论
		dp[i]=(dp[i-9]+dp[i-10])%mod;
	scanf("%lld",&T);
	while(T--)
	{
		ll n,m,ans=0;
		scanf("%lld%lld",&n,&m);
		while(n>0)//按位计算答案 
		{
			ll temp=n%10;
			ans=(ans+dp[m+temp])%mod;
			n/=10;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

D. GCD and MST

题目链接:https://codeforces.ml/contest/1513/problem/D

题目大意:

有t组数据,给定n和p,以及一个长度为n的a序列。这些序列上的数作为一个个顶点构成一张图。边的形成条件为:

  • 如果一段区间[i,j]满足:gcd(a[i],a[i+1],······,a[j-1],a[j])=min(a[i],a[i+1],······,a[j-1],a[j]),则i,j之间存在一条长度为min(a[i],a[i+1],······,a[j-1],a[j])的边。
  • 如果i+1=j,则i,j之间存在一条长度为p的边

题目要求求出这个图的最小生成树的边权和。

题目分析:

1.假设图已经建立好了,那么一种最简单的生成树的方式就是使用所有i和i+1的边。可是,这个树并不一定是最小生成树。
2.我们想到了最小生成树的kruskal算法,从小到大枚举边权进行添加。边权的值即为:min(a[i],a[i+1],······,a[j-1],a[j])。如果边权w>=p,那么显然不需要考虑。
3.但在这个地方,我们不采取加边的方式,而是采用替代的方式,因为我们已经有了一个现成的树了,所以用新的边替代使用1中的方法构造出来的边。很明显,为了不出现环,每一条边只能被替代一次。
4.最后的困难就是解决边的问题。我们可以把a序列从小到大排序,然后从小到大枚举a。假设新的边的边权就是a[pi],然后从pi向左右拓展,判断是否满足题目构成边的条件(gcd和min的条件)。如果满足且这条边还没被替代过,则用这个去替代。否则就尝试下一条边。
5.4中贪心的方法成立的原因无需赘述,就是kruskal算法的原理。

正解代码:

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>

using namespace std;
typedef long long ll;
const ll maxn=2e5+10;
struct node
{
	ll num;
	ll pos;
}point[maxn];
ll a[maxn],n,p,ans;
bool vis[maxn];
bool cmp(node x,node y)
{
	if(x.num!=y.num)
		return x.num<y.num;
	return x.pos<y.pos;
}
int main()
{
	ll T;
	scanf("%lld",&T);
	while(T--)//原本假设所有的边都是i和i+1构成的
	{
		memset(vis,false,sizeof(vis));//vis表示i到i+1的边是否被别的边所替代,一条边只能被替代一次 
		ans=0;
		scanf("%lld%lld",&n,&p);
		for(ll i=0;i<n;i++)
			scanf("%lld",&a[i]);
		for(ll i=0;i<n;i++)
		{
			point[i].num=a[i];
			point[i].pos=i;
		}	
		sort(point,point+n,cmp);
		ll times=0;
		for(ll i=0;i<n;i++)//以point[i].num为gcd,类似kruskal的思路 
		{
			ll val=point[i].num;
			ll now=point[i].pos;
			if(val>=p)//大于等于p则可以直接用p连接 
				break;
			while(now>0)//向左拓展 
			{
				if(vis[now-1] || a[now-1]%val!=0)
					break;
				vis[now-1]=true;
				ans+=val;
				now--;
			}
			now=point[i].pos;
			while(now<n-1)//向右拓展 
			{
				if(vis[now] || a[now+1]%val!=0)
					break;
				vis[now]=true;
				ans+=val;
				now++;
			}
		}
		for(ll i=0;i<n-1;i++)//如果i和i+1之间仍然需要一条边,则添加 
			if(!vis[i])
				ans+=p;
    	printf("%lld\n",ans);
	}	
	
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值