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

本文解析了CodeForces平台上的四道算法题目,包括完全平方数判断、构造数组求最大和、乘积模意义下的数列构建以及区间分割问题。提供了详细的思路分析与代码实现。

A. Perfectly Imperfect Array:

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

题目大意:

有t组数据,每组数据有n个数,如果这n个数中存在一个数不为完全平方数,则输出YES,否则就输出NO。
数据范围:1<=t<=100,1<=n<=100,1<=ai<=10000

题目分析:

没有任何技术含量,直接开放判断开方的结果是不是刚好为整数。如果存在不为整数的数,则说明出现了不为完全平方数的数,输出YES,否则就输出NO。

正解程序:

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

using namespace std;
typedef long long ll;
ll T,n;
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		ll flag=0;
		scanf("%lld",&n);
		for(ll i=1;i<=n;i++)
		{
			ll temp;
			scanf("%lld",&temp);
			double ano=sqrt((double)temp);
			if(abs(ano-(ll)ano)>1e-8)//double类型的比较大小最好使用eps
				flag=1;//因为要把所有数据都读入,所以先标记,结束时再输出
		}
		if(flag)
			printf("YES\n");
		else
			printf("NO\n");
	}
	
	return 0;
}

B. AND 0, Sum Big:

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

题目大意:

有t组数据,每组有一个n和一个k,要求构造一个长度为n的数组a,使得数组满足三个条件:

  • 数组中的每个数都满足0<=ai<=2k-1。
  • 数组中所有数按位与的值为0。
  • 数组中所有数的和尽可能大。

要求输出能够构造出来的数组a有多少个,由于答案可能很大,输出答案对109+7取余。

题目分析:

1.因为所有数都满足0<=ai<=2k-1,所以都可以被转化为k位二进制数。
2.首先,要满足所有数按位与的值为0,则说明要求n个数满足在k位二进制下存在一些数使得这k位为0。即:对于第i位(1<=i<=k)至少有一个数是为0的。
3.我们考虑如何使这个数组满足第三个条件。因为对于第i位(1<=i<=k)只要有一个数是为0的,那最终按位与的结果中第i位就为0。而使第i位为0就相当于减少了2i-1,所以要使得最终的和尽可能大,自然是使用最少的0完成这一过程。所以只需要n个数承担k个0即可。
4.因为一个数可以承担多个0,所以答案很简单,不需要更多的排列组合的知识。对于k个0,每一个0都由n个数中的某一个来承担,所以答案就nk,最后答案再对109+7取余即可。

正解代码:

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

using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll T,n,k;
ll quickmi(ll a,ll p)//快速幂,方便取余
{
	ll ans=1;
	while(p)
	{
		if(p&1)
			ans=ans*a%mod;
		a=a*a%mod;
		p>>=1;
	}
	return ans;
}
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld%lld",&n,&k);
		printf("%lld\n",quickmi(n,k));//求出n^k
	}
	
	return 0;
}

C. Product 1 Modulo N:

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

题目大意:

给定一个n,要求选取出一些不重复的数形成一个数组num,num中的数满足1<=numi<=n-1,最后使得数组a所有数的乘积对n取余的结果为1。要求满足这个条件的数组的长度尽可能大。

题目分析:

1.我们设num数组的乘积为a,所以题目的意思大致相当于a≡1(mod n),其实就是:gcd(n,a%n)=1。我们根据欧几里得定律,可以知道:gcd(n,a%n)=gcd(a,n)=1。于是所有与n不互质的数不能出现在这个序列中。
2.将所有与n不互质的数乘起来,假设乘积为b,并对n取余,得到b≡x(mod n)。显然,x一定是1~n-1的某一个值,且由1可知,x一定与n互质。那么答案就是剩下的数去掉x即可。因为根据同余的性质,则有:b/x≡x/x(mod n/gcd(x,n)),即b/x≡1(mod n)。当然,对于x=1的情况就不需要去除了。

正解代码:

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

using namespace std;
typedef long long ll;
const ll maxn=100010;
ll gcd(ll a,ll b)
{
	if(!b)
		return a;
	return gcd(b,a%b);
}
ll ans[maxn],cnt=0,n;
int main()
{
	scanf("%lld",&n);
	ll fina=1;
	for(ll i=1;i<=n;i++)
	{
		if(gcd(i,n)==1)//判断是否互质
		{
			ans[++cnt]=i;
			fina=fina*i%n;//计算乘积
		}
	}
	if(fina!=1)//如果x不等于1
	{
		printf("%lld\n",cnt-1);
		for(ll i=1;i<=cnt;i++)
			if(ans[i]!=fina)//去除fina
				printf("%lld ",ans[i]);
	}
	else//x=1,直接输出
	{
		printf("%lld\n",cnt);
		for(ll i=1;i<=cnt;i++)
			printf("%lld ",ans[i]);
	}
	
	return 0;
}

D. Cut and Stick:

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

题目大意:

给一个长度为n的数组a以及q组询问,每组询问包含l和r。题目希望将a[l] ~ a[r]分成若干子集,使得长度为x的子集中没有出现频率大于等于 ⌈ \lceil x 2 \frac{x}{2} 2x ⌉ \rceil 的数。问最少需要将a[l] ~ a[r]分成多少段。
数据范围:1<=n,q<=3*105,1<=ai<=n,1<=l<=r<=n。

题目分析:

1.因为题目询问出现频率大于等于 ⌈ \lceil x 2 \frac{x}{2} 2x ⌉ \rceil ,所以这个频率一定是这一个子集里面最大的,或许需要一些处理最值的方法。
2.对于一个数在l ~ r中间出现了多少次的问题,我们可以通过记录某一个数在数组中出现的位置,然后用二分的方式去得到答案。这样的复杂度就是O(logn)而不是直接计数的O(n)了。考虑ai的数据范围,我们可以直接使用vector来完成这一过程。
3.既然我们处理的是区间上的最值问题,那么我们为什么不能用线段树来维护一段区间上出现频率最高的数是什么呢?所以我们可以先用线段树区间维护出现频率的最大值。
4.那么如何来计算答案呢?我们假设一个长度为x的子集,出现频率的最大值为f,这个数为k。

  • f< ⌈ \lceil x 2 \frac{x}{2} 2x ⌉ \rceil ,那么答案就是1。
  • f>= ⌈ \lceil x 2 \frac{x}{2} 2x ⌉ \rceil ,那剩下的这些所有的元素有x-f个。由于向上取整的关系,那么最多可以和x-f+1个k结合成1个合法的子,剩下的k都自己构成一个子集。这样最终的答案就是:f-(x-f+1)+1=2*f-x。

正解代码:

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

using namespace std;
typedef long long ll;
const ll maxn=300010;
ll num[maxn],tree[4*maxn];
vector<ll> times[maxn];
ll getnum(ll l,ll r,ll pos)//二分得到在l,r之间pos出现的次数
{
    return upper_bound(times[pos].begin(),times[pos].end(),r)-lower_bound(times[pos].begin(),times[pos].end(),l);
}
ll mymax(ll x,ll y,ll l,ll r)//一个自定义比大小函数,用于维护线段树
{
	ll t1=getnum(l,r,x),t2=getnum(l,r,y);
	if(t1>t2)
		return x;
	return y;
}
void build(ll pos,ll l,ll r)//线段树的构建
{
	if(l==r)
	{
		tree[pos]=num[l];
		return;
	}
	ll mid=(l+r)>>1;
	build(pos<<1,l,mid);
	build(pos<<1|1,mid+1,r);
	tree[pos]=mymax(tree[pos<<1],tree[pos<<1|1],l,r);
}
ll query(ll pos,ll l,ll r,ll s,ll e)//线段树的查询
{
	if(s<=l && r<=e)
		return getnum(s,e,tree[pos]);//最终的答案是频率,所以得先查询出现次数
    ll mid=(l+r)>>1,t1=0,t2=0;
    if(s<=mid)
    	t1=query(pos<<1,l,mid,s,e);
    if(e>mid)
    	t2=query(pos<<1|1,mid+1,r,s,e);
    return max(t1,t2);
}
int main()
{
	ll n,q;
	scanf("%lld%lld",&n,&q);
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld",&num[i]);
		times[num[i]].push_back(i);//记录num[i]在i位置出现过
	}
	build(1,1,n);
	for(ll i=1;i<=q;i++)
	{
		ll l,r;
		scanf("%lld%lld",&l,&r);
		printf("%lld\n",max(1ll,2*query(1,1,n,l,r)-(r-l+1)));//得到答案
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值