Codeforces Round 904 (Div. 2)补题

Simple Design

题目大意:我们定义k-beautiful数的意思是一个数的数位和可以整除k,现给定整数x,k,求出最小的y,满足y>=x,y是k-beautiful数。

思路:这题直接暴力实际上可以写,因为k的范围在1到10之间。

#include<bits/stdc++.h>
using namespace std;
int getsum(int x)
{
	int sum=0;
	while(x)
	{
		sum += x%10;
		x/=10;
	}
	return sum;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int x,k;
		scanf("%d%d",&x,&k);
		for(int i=x;;i++)
		{
			if(getsum(i)%k==0) 
			{
				printf("%d\n",i);
			    break;
			}
		}
	}
}

Haunted House

题目大意:给一个整数n和一个01串,我们可以交换01串的任意相邻的两位,使01串表示的十进制数可以整除2^i(1<=i<=n),另外01串的长度也为n,求对于每个i的最小操作数,如果不存在就输出-1.

思路:我们注意到可以整除2^i,从后往前i位都是0.将一个0往后移动k位,就要操作k次。我们先统计字串串0的个数,如果0的个数小于i,那么直接输出-1即可,如果大于等于,那么就要考虑如果移动,尽可能少的移动,我们可以定义一个数组来表示要使末尾有k个0,我们需要操作多少次。

我们来仔细讨论以下这个数组的值该如何获取:
首先,我们定义一个变量i,表示从后往前看,当前访问到到第几位,用k表示访问到第几个0,或是说如果访问到0的话,这个0应该被放到从后往前的第几位,那么我们访问到第i位,这一位上的0需要被放到第k位,那么就是要移动i-k次。前面再加上前面k-1个0的操作数,那么就得到要使末尾k个都是0,我们需要移动多少次。

从后往前访问,当访问到0的时候,a[k]=i-k(k=1,i=1开始)

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n;
		scanf("%lld",&n);
		string s;
		cin>>s;
		int k=1;
		for(int i=s.size()-1,j=1;i>=0;i--,j++)
		{
			if(s[i]=='0')
			{
				a[k]=j-k+a[k-1];
				k++;
			}
		}
		int cnt=k-1;
		for(int i=1;i<=n;i++)
		{
			if(cnt<i) printf("-1 ");
			else printf("%lld ",a[i]);
		}
		printf("\n");
	}
}

Medium Design

题目大意:我们有一个长为m的数组a[],a[]的初值是0,我们给定n个区间,我们可以从中选择若干个区间,将选定区间内的每个ai都加1,我们要求最后a[]中max-min的最大值。

思路:我们考虑可以发现,最大值出在叠加最多的位置,那么最小值呢,实际上在开头或者结尾的位置,我们来简单证明一下:

对于区间[l,r],如果这段区间贯穿整个数组,那么各处都一样,如果不贯穿,那么开头或者结尾一定有一个没被包含,那么如果有两段区间,有没有可能出现中间比首位小呢,不可能,如果包含首位,它们可能至少被包含一次,可以证明,中间如果有断点,那么选两段区间和选一段区间效果是一样的,所以最小值一定可以取在开头或者结尾。

 那么我们该如何选择区间呢,主要是要怎么确定叠加区间有多少个。这里有个特别妙的思路,我们将每段区间的左右端点都记录一下,统一排序,然后开始遍历,遍历到左端点,那么叠加区间的数量就加1,如果访问到右端点,那么就证明再往后,我们需要踢掉一个区间,我们只要及时取最大值即可保证找到最大的重叠区。现在还有一个需要考虑的问题就是最小值,我们该怎么办呢,我们如果仅仅按照前面叙述的来实现的话,我们没办法确定首尾是否被覆盖,或者是否被解除覆盖,那么我们就要再将该区间的另外一个端点记录一下,我们在加入一个区间的时候,需要看它是否覆盖首尾,记录一下,每次访问一个新的记录,更新一下答案即可。另外要注意,记录数组的空间一定要开够,越界的话什么问题都可能出现。

#include<bits/stdc++.h>
using namespace std;
const int N=200010;//越界的话各种问题都可能出现
struct lr
{
	int v,w,e;
}q[N];
bool cmp(lr a,lr b)
{
	if(a.v!=b.v)return a.v<b.v;
	else return a.w>b.w;//先放入再踢,可能有节点重合
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		int k=0;
		for(int i=0;i<n;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			q[k++]={a,1,b};
			q[k++]={b,-1,a};
		}
		sort(q,q+k,cmp);
		int first=0,last=0,c=0,ans=0;
		for(int i=0;i<k;i++)
		{
			if(q[i].w==1)
			{
				c++;
				if(q[i].v==1) first++;
				if(q[i].e==m) last++;
 			}
 			else
 			{
 				c--;
 				if(q[i].e==1) first--;
 				//last不用管,如果到last了,也访问到头了
			}
			int mi=min(first,last);
			ans = max(ans,c-mi);
		}
		printf("%d\n",ans);
	}
}

Counting Rhyme

题目大意:我们称满足如下条件的数对(i,j)为好数对:

1<=i<j<=n,对任意1<=k<=n,不存在a[k]使得a[i]%a[k]==a[j]%a[k]==0(a[]是给定的大小为n的数组)

求有多少个好数对。

思路:我们分析一下可以发现,相当于数组中不能存在a[i]和a[j]的公因数,我们如果暴力枚举出所有的数对,然后再去判断数组中的数是否是它的因数,这样肯定会超时,那么我们该怎么办呢?可以转换思路,我们既然不能判断出每个数是否是另外一个数的因数,但是我们可以找出每个数的倍数的个数,因为一个数的因数不好找,但是它的倍数是很好找的,每次加上它本身就好,那么我们就要先统计出每个数的出现次数。这样就可统计出每个数的倍数有多少个。

我们注意到,如果我们统计出每个数有多少个数能够整除它,如果这个数没有在数组中出现,那么这些个数就可以凑出合适的对数,当然,你会问,就算这些数不存在,那么它们的因数存在,不也不可以,那么如果我们在统计的时候考虑到这一点,即一个数的因数出现,那么我们就视作这个数出现,这样处理后剩下的不就是本身及因数都没有出现,但是有数可以整除它,那么再转化一下意思就是有些数对的公因数并未出现在数组中,那么这不就是我们想要找的数对吗。这里我们再加一个新的数组来表示每个数是否出现即可。

我们在统计每个数的倍数的时候,还需要更新一下每个数是否出现的标记,一个数的因数出现,就视为这个数已经出现了。此时循环结束后,我们想要的每个数有多少个数能整除它就统计出来了。如果有s个能整除它,那么可以构成的数对就是s*(s-1)/2。

然后我们来统计答案,可以直接循环访问每个数吗,当然也是不可以的,因为一对数它们可能既可以整除g,也能整除2*g,还能整除3g,我们用来记录g的数组肯定将它们都计进去了,那么该如何避免重复计算呢?有个思路就是,从大到小累计,同时我们在累计每个数之前将它的倍数产生的个数都减掉,这样就可以避免重复计算了。

至此题目解决,时间复杂度为O(nlogn).

#include<bits/stdc++.h>
using namespace std;
#define int long long
int g[1000010],st[1000010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n;
		scanf("%lld",&n);
		for(int i=1;i<=n;i++) g[i]=st[i]=0;
		for(int i=1;i<=n;i++)
		{
			int x;
			scanf("%lld",&x);
			g[x]++;
			st[x]=1;
		}
		//O(nlogn)
		for(int i=1;i<=n;i++)
		{
			for(int j=2*i;j<=n;j+=i)
			{
				g[i]+=g[j];
				st[j]|=st[i];
			}
			g[i]=(g[i]-1)*g[i]/2;
		}
		int ans=0;
		for(int i=n;i>=1;i--)
		{
			for(int j=2*i;j<=n;j+=i) g[i] -= g[j];
			if(!st[i]) ans += g[i];
		}
		printf("%lld\n",ans);
	}
}

时间复杂度:

n+n/2+n/3+...n/n=nlogn

证明如下:时间复杂度证明

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值