7

我正在尝试解决在线法官问题: 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)生成数组中大小为 n 的下一个子集,A如果没有子集,则返回 0,否则返回 1。它基于stackoverflow 问题中接受的答案所描述的算法。

lcm(a,b)函数返回 a 和 b 的 lcm。该get_lcm(n)函数返回 中所有元素的 lcm A。它使用属性:LCM(a,b,c) = LCM(LCM(a,b),c)

当我将问题提交给法官时,它会给我一个“超出时间限制”。如果我们使用蛮力解决这个问题,我们只能得到 50% 的分数。

由于最多可以有 2^20 个子集,我的算法可能会很慢,因此我需要一个更好的算法来解决这个问题。

编辑:

在编辑我的代码并将函数更改为欧几里得算法后,我得到了错误的答案,但我的代码在时间限制内运行。它给了我对示例测试的正确答案,但对任何其他测试用例都没有;这是我运行代码的ideone的链接,第一个输出是正确的,但第二个不是。

我解决这个问题的方法正确吗?如果是,那么我在代码中犯了一个错误,我会找到它;否则谁能解释什么是错的?

4

7 回答 7

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
于 2013-01-13T04:09:16.603 回答
1

k通常,一个子集的最小公倍数s_i将超过远小于 20。所以你需要尽早停止Lk

可能只是插入

if (temp >= L) {
    break;
}

while(next_subset(i)){
  temp = getlcm(i);

就足够了。

此外,如果 中有任何1s,则s_i所有数字都可以被 1 整除。

我认为以下会更快:

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;
}

调用它

unsigned S[N] = {...};
inex(S, N, L-1);

您无需在任何地方为 0 添加 1,因为 0 可以被所有数字整除,计算1 <= k < L不能被 any 整除的数字的计数s_i

于 2013-01-13T04:03:39.323 回答
1

恐怕您对问题的理解可能不正确。

你有 L。你有一组由 K 个元素组成的 S。您必须计算 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

于 2013-01-13T04:53:26.487 回答
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 和 S1*S2*S3 之间的所有 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 一样大。

于 2013-01-13T05:28:11.557 回答
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--;
     }
 }
于 2013-01-13T05:44:55.063 回答
1

@A.06:您是在 opc 上使用用户名 linkinmew 的那个,仪式?

无论如何,答案只需要你制作所有可能的子集,然后应用包含排除原则。这将完全在给定数据的时间范围内。为了制作所有可能的子集,您可以轻松定义递归函数。

于 2013-01-14T17:39:29.080 回答
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 分割

于 2021-05-21T08:01:41.720 回答