我正在尝试解决在线裁判问题:http://opc.iarcs.org.in/index.php/problems/LEAFEAT
问题简短:
如果我们给出一个整数L和一组N个整数s1,s2,s3..sN,我们必须找到从0到L-1的数字,这些数字不能被任何'si'整除
例如,如果给出L = 20
和S = {3,2,5}
,那么从0到19的6个数字不能被3,2或5整除。
L <= 1000000000且N <= 20。
我使用包含 - 排除原则来解决这个问题:
/*Let 'T' be the number of integers that are divisible by any of the 'si's in the
given range*/
for i in range 1 to N
for all subsets A of length i
if i is odd then:
T += 1 + (L-1)/lcm(all the elements of A)
else
T -= 1 + (L-1)/lcm(all the elements of A)
return T
以下是解决此问题的代码
#include <stdio.h>
int N;
long long int L;
int C[30];
typedef struct{int i, key;}subset_e;
subset_e A[30];
int k;
int gcd(a,b){
int t;
while(b != 0){
t = a%b;
a = b;
b = t;
}
return a;
}
long long int lcm(int a, int b){
return (a*b)/gcd(a,b);
}
long long int getlcm(int n){
if(n == 1){
return A[0].key;
}
int i;
long long int rlcm = lcm(A[0].key,A[1].key);
for(i = 2;i < n; i++){
rlcm = lcm(rlcm,A[i].key);
}
return rlcm;
}
int next_subset(int n){
if(k == n-1 && A[k].i == N-1){
if(k == 0){
return 0;
}
k--;
}
while(k < n-1 && A[k].i == A[k+1].i-1){
if(k <= 0){
return 0;
}
k--;
}
A[k].key = C[A[k].i+1];
A[k].i++;
return 1;
}
int main(){
int i,j,add;
long long int sum = 0,g,temp;
scanf("%lld%d",&L,&N);
for(i = 0;i < N; i++){
scanf("%d",&C[i]);
}
for(i = 1; i <= N; i++){
add = i%2;
for(j = 0;j < i; j++){
A[j].key = C[j];
A[j].i = j;
}
temp = getlcm(i);
g = 1 + (L-1)/temp;
if(add){
sum += g;
} else {
sum -= g;
}
k = i-1;
while(next_subset(i)){
temp = getlcm(i);
g = 1 + (L-1)/temp;
if(add){
sum += g;
} else {
sum -= g;
}
}
}
printf("%lld",L-sum);
return 0;
}
next_subset(n)
在数组A
中生成大小为n的下一个子集,如果没有子集则返回0,否则返回1.它基于接受的答案描述的算法this stackoverflow问题。
lcm(a,b)
函数返回a和b的lcm。
get_lcm(n)
函数返回A
中所有元素的lcm。
它使用属性:LCM(a,b,c) = LCM(LCM(a,b),c)
当我向法官提交问题时,它给出了“超出时间限制”。如果我们使用强力解决这个问题,我们只得到50%的分数。
由于最多可能有2 ^ 20个子集,我的算法可能会很慢,因此我需要一个更好的算法来解决这个问题。
修改
编辑我的代码并将函数更改为Euclidean算法后,我得到了错误的答案,但我的代码在时间限制内运行。它给出了我对示例测试的正确答案,但没有给出任何其他测试用例;这里是ideone的链接,我运行了我的代码,第一个输出是正确的,但第二个输出没有。
我对此问题的处理方法是否正确?如果是,那么我在我的代码中犯了一个错误,我会找到它;否则有人可以解释一下是什么问题吗?
答案 0 :(得分:2)
您也可以尝试更改lcm
功能以使用Euclidean algorithm。
int gcd(int a, int b) {
int t;
while (b != 0) {
t = b;
b = a % t;
a = t;
}
return a;
}
int lcm(int a, int b) {
return (a * b) / gcd(a, b);
}
至少在Python中,两者之间的速度差异非常大:
>>> %timeit lcm1(103, 2013)
100000 loops, best of 3: 9.21 us per loop
>>> %timeit lcm2(103, 2013)
1000000 loops, best of 3: 1.02 us per loop
答案 1 :(得分:1)
通常情况下,k
的{{1}}子集的最低公倍数将超过s_i
L
远小于20,因此您需要提前停止。
可能只需插入
k
后
if (temp >= L) {
break;
}
就足够了。
此外,如果while(next_subset(i)){
temp = getlcm(i);
中有1
个,则快捷方式,所有数字都可以被1整除。
我认为以下内容会更快:
s_i
用
调用它unsigned gcd(unsigned a, unsigned b) {
unsigned r;
while(b) {
r = a%b;
a = b;
b = r;
}
return a;
}
unsigned recur(unsigned *arr, unsigned len, unsigned idx, unsigned cumul, unsigned bound) {
if (idx >= len || bound == 0) {
return bound;
}
unsigned i, g, s = arr[idx], result;
g = s/gcd(cumul,s);
result = bound/g;
for(i = idx+1; i < len; ++i) {
result -= recur(arr, len, i, cumul*g, bound/g);
}
return result;
}
unsigned inex(unsigned *arr, unsigned len, unsigned bound) {
unsigned i, result = bound, t;
for(i = 0; i < len; ++i) {
result -= recur(arr, len, i, 1, bound);
}
return result;
}
您无需在任何地方为0添加1,因为0可被所有数字整除,计算不能被任何unsigned S[N] = {...};
inex(S, N, L-1);
整除的数字1 <= k < L
的数量。
答案 2 :(得分:1)
我担心你的问题理解可能不正确。
你有L.你有一组K元素。你必须计算L / Si商的总和。对于L = 20,K = 1,S = {5},答案仅为16(20-20 / 5)。但是K> 1,所以你必须考虑常见的倍数。
为什么要遍历子集列表?它不涉及子集计算,只涉及除法和多次。
你有K个不同的整数。每个数字都可以是素数。你必须考虑常见的倍数。就是这样。
修改强>
L = 20且S = {3,2,5}
叶子可以吃3 = 6 叶子可以吃2 = 10 叶子可以吃5 = 4
S的公倍数,小于L,而不是S = 6,10,15
实际吃叶= 20/3 + 20/2 + 20/5 - 20/6 - 20/10 - 20/15 = 6
答案 3 :(得分:1)
创建带有L个条目的标志数组。然后标记每个触摸的叶子:
for(each size in list of sizes) {
length = 0;
while(length < L) {
array[length] = TOUCHED;
length += size;
}
}
然后找到未触动过的树叶:
for(length = 0; length < L; length++) {
if(array[length] != TOUCHED) { /* Untouched leaf! */ }
}
请注意,没有乘法,也没有分割;但是你需要大约1 GiB的RAM。如果RAM有问题,可以使用位数组(最大120 MiB)。
这只是一个开始,因为有重复的模式可以复制而不是生成。第一个模式是0到S1 * S2,下一个是从0到S1 * S2 * S3,下一个是从0到S1 * S2 * S3 * S4等。
基本上,您可以设置S1触摸的所有值,然后将S2从0设置为S1 * S2;然后将模式从0复制到S1 * S2,直到进入S1 * S2 * S3并将所有S3设置在S3和S1 * S2 * S3之间;然后复制该模式,直到你进入S1 * S2 * S3 * S4并将所有S4设置在S4和S1 * S2 * S3 * S4之间,依此类推。
下一步;如果S1 * S2 * ... Sn小于L,您知道图案将重复并且可以生成从图案中S1 * S2 * ... Sn到L的长度的结果。在这种情况下,阵列的大小只需要是S1 * S2 * ... Sn,并且不需要是L.
最后,如果S1 * S2 * ... Sn大于L;然后你可以生成S1 * S2 * ...(Sn-1)的模式,并使用该模式从S1 * S2 * ...(Sn-1)到S1 * S2 * ... Sn创建结果。在这种情况下,如果S1 * S2 * ...(Sn-1)小于L,则阵列不需要与L一样大。
答案 4 :(得分:1)
您可以跟踪每个尺寸的下一个触摸叶片的距离。到下一个触摸的叶子的距离将是最小的距离,并且您将从所有其他距离中减去该距离(并且每当距离为零时进行换行)。
例如:
int sizes[4] = {2, 5, 7, 9};
int distances[4];
int currentLength = 0;
for(size = 0 to 3) {
distances[size] = sizes[size];
}
while(currentLength < L) {
smallest = INT_MAX;
for(size = 0 to 3) {
if(distances[size] < smallest) smallest = distances[size];
}
for(size = 0 to 3) {
distances[size] -= smallest;
if(distances[size] == 0) distances[size] = sizes[size];
}
while( (smallest > 1) && (currentLength < L) ) {
currentLength++;
printf("%d\n", currentLength;
smallest--;
}
}
答案 5 :(得分:1)
@ A.06:你用opc上的用户名linkinmew,rite?
无论如何,答案只需要你制作所有可能的子集,然后应用包含排除原则。这将完全落在给定数据的时间范围内。为了制作所有可能的子集,你可以很容易地定义一个递归函数。
答案 6 :(得分:0)
我不知道编程,但在数学中,有一个定理适用于具有 GCD 1 的集合 L=20, S=(3,2,5) (1-1/p)(1-1/q)(1-1/r).....等等 (1-1/3)(1-1/2)(1-1/5)=(2/3)(1/2)(4/5)=4/15 4/15 表示每组 15 个数字中有 4 个数字不能被任何数字整除,其余的可以手动计算,例如。 16, 17, 18, 19, 20 (只有 17 和 19 表示只有 2 个数字不能被任何 S 整除) 4+2=6 6/20 表示前 20 个数字中只有 6 个数字不能被任何 s 整除