倍集 --组合数学习题练习

倍集

题目链接:https://ac.nowcoder.com/acm/problem/21335?&headNav=acm
来源:牛客网

题目描述
S1 和 S2是两个整数的集合,如果S1里面的每一个数x都存在一个S2里面的y是x的倍数
那么我们称S2是S1的倍集
给你四个整数A,B,C,D
有一个集合S包含所有的x满足 A <= x <= B C <= x <= D
求出S的一个元素最少的子集T,使得T是S的倍集
你只需要输出T的元素的个数即可
输入描述:
输入一行包含四个整数A,B,C,D
1 ≤ A ≤ 1010
A ≤ B ≤ 1010
B + 1 ≤ C ≤ 1010
C ≤ D ≤ 1010
输出描述:
输出一个整数
示例1:
输入

1 1 2 2

输出

1

示例2
输入

1 2 3 4

输出

2

示例3
输入

2 3 5 7

输出

3

示例4
输入

1 10 100 1000

输出

500

示例5
输入

1000000000 2000000000 9000000000 10000000000

输出

1254365078

题解:

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
int main(){
	ll a,b,c,d;
	cin>>a>>b>>c>>d;
	a=max(a,b/2+1);//区间[a,b]如果出现二倍区间替换
	c=max(c,d/2+1);//同上
	ll ans=d-c+1;
	//替换之后[c,d]区间数必须选,除本身不存在某个数是他倍数
    //但是[a,b]中某些数可以拿[c,d]的数替换
	ll right,k;
	//枚举倍数k缩小区间[a,b]
	for(ll left=a;left<=b;left=d/k+1)
	{			//由于向零取整,left*k<=d 
		k=d/left;//第一次循环的时候确定最大能缩小的倍数,以后的每次循环递减倍数直到k=1
		right=min(b,(c-1)/k);//不能缩到的右边界
		if(right>=left)ans+=right-left+1;
	}
	cout<<ans<<endl;
	return 0;
}

思路:
代码是我剽窃大佬的,但是没看懂。于是我又剽窃了另一位大佬的思路。索性还是明白了一些,所以我就综合大佬智慧的结晶,写写我对这段代码的理解。这个题就是出去S集合中那些倍数仍在S集合中的数,生成集合T,然后统计集合T的个数(代码中用ans表示)
首先,“typedef long long ll;”这个语句将长整形定义为ll,为什么要用长整型呢?因为题目的示例5出示的数据区间是10^10级别的,长整型能支持的整数范围是-2^63到2^63-1,所以考虑用长整型。
其次使用a=max(a,b/2+1);c=max(c,d/2+1); 将集合的范围初步缩小,先舍去集合中那些2倍仍在集合中的数(意思就是如果x在集合中,且2*x也在集合中,则舍弃x)。通过移动左边界来实现。
现在集合S被分为左右两个部分,即区间[a,b]、[c,d].显然区间[c,d]的所有整数已经没有倍数了(因为连2倍都没有,更别说3倍4倍啦)所以给ans加上区间[c,d]整数的个数。现在要做的事就是记录区间[a,b]里在区间[c,d]里存在倍数的数的个数。呃,这话说的我自己都听不懂,还是用符号表示一下吧。设x为区间[a,b]里面的一个整数,y为区间[c,d]里的一个整数,如果y是x的倍数,则ans加一.我们要做的就是记录所有这样x的个数。这个功能就是那个我看不太懂的for循环实现的。。。
在说这个for循环之前需要先明白一个小知识。假设下面是一个坐标轴(x<a<b) 在这里插入图片描述
若存在a/x = b/x,则x在区间(a,b)中不存在倍数。举个例子来解释一下,假设x = 7,a = 21, b = 28 那么区间(21,28)里的所有整数除以7都等于3.而7在区间(21,28)中也没有倍数。还有点迷糊吧,看看下面这张图。
在这里插入图片描述
分别在区间(21,28)、(28,35)、(35,42)中的每一个整数除以7都分别是3、4、5。所以只要区间(a,b)不跨越 7 的倍数构成的这些节点,即满足 a/x = b/x,就不会包含 7 的倍数(如区间(a’,b’))。反之,如果区间包含了 7 的倍数构成的结点,那么 a/x != b/x(如区间(a,b))
好啦,现在来看这个for循环。大体思想就是从a开始往后判断在(c,d)里面有没有倍数,直到b结束。其中最主要的三个语句就是left=d/k+1k=d/left;right=min(b,(c-1)/k);可以看出来left每次循环表示的 就是区间(a,b)目前尚未判断过的数的左边界,它是逐渐向右挪的;right也跟着往右挪,而且它不会超过b;k表示倍数。结合前面的说法,c和d同时除一个数,得到的结果如果相等,那么这个结果在区间(c,d)内没有倍数。在这个循环里c和d同时除的就是倍数k,得到的结果就分别是left和right.left从a开始往后试(但好像不是一个挨一个,我不明白为什么可以,但大佬的目的应该是要减少循环的次数)根据left的值确定目前最大倍数k和无法达到的边界right。此时left=d/k,right=(c-1)/k (c-1应该就是排除边界的影响吧)当left=right时证明此时left指向的那个数在区间[c,d]内没有倍数,ans值加一。在大佬的代码中判断条件是 right>=left ,然后把right和left中间的整数个数加到ans里面。但是我觉得在这个循环里面不可能有right>left的时候吧,于是我去掉了’>',于是代码就不能完全通过了。。。所以说还是大佬厉害,不能随意揣测大佬的想法,不能轻易怀疑大佬的判断~

补充零碎知识:
(1)algorithm头文件中的reverse函数:reverse(it,it2) 可以将数组指针在[it,it2)之间的元素或容器的迭代器在[it,it2)范围内的元素进行反转。

#include<stdio.h>
#include<algorithm>
using namespace std;
int main()
{
    int a[10]={10,11,12,13,14,15};
    reverse(a,a+4);
    for(int i=0;i<6;i++){
        printf("%d ",a[i]);
    }
    return 0;
}

运行结果:

13 12 11 10 14 15

这是一个前辈总结的algorithm头文件下常用函数:https://blog.csdn.net/hy971216/article/details/80056933

(2)typedef的用法:
https://blog.csdn.net/superhoy/article/details/53504472
在这个题主要是用typedef定义一个长整型的数据类型。给它替换成 ll

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值