7

私はオンライン裁判官の問題を解決しようとしています: http://opc.iarcs.org.in/index.php/problems/LEAFEAT

要するに問題:

整数 L と N 個の整数 s1、s2、s3..sN のセットが与えられた場合、0 から L-1 までの si のいずれでも割り切れない数がいくつあるかを見つけなければなりません。

たとえば、与えられた場合、 L = 203、2、5S = {3,2,5}で割り切れない 0 から 19 までの 6 つの数字があるとします。

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 の次のサブセットを生成します。サブセットがない場合は 0 を返し、それ以外の場合は 1 を返します。これは、このスタックオーバーフローの質問Aで受け入れられた回答で説明されているアルゴリズムに基づいています。

このlcm(a,b)関数は、a と b の lcm を返します。このget_lcm(n)関数は、 内のすべての要素の lcm を返しますA。プロパティを使用します:LCM(a,b,c) = LCM(LCM(a,b),c)

ジャッジに問題を提出すると、「制限時間超過」と表示されます。これをブルート フォースで解決すると、50% のマークしか得られません。

最大 2^20 のサブセットが存在する可能性があるため、私のアルゴリズムは遅くなる可能性があるため、この問題を解決するにはより良いアルゴリズムが必要です。

編集:

コードを編集して関数をユークリッド アルゴリズムに変更した後、間違った答えが得られますが、コードは制限時間内に実行されます。サンプル テストに対する正しい答えが得られますが、他のテスト ケースに対する正しい答えは得られません。コードを実行したideoneへのリンクを次に示します。最初の出力は正しいですが、2 番目の出力は正しくありません。

この問題に対する私のアプローチは正しいですか? もしそうなら、私は自分のコードで間違いを犯しており、それを見つけます。そうでなければ、誰かが何が間違っているのか説明してもらえますか?

4

7 に答える 7

2

ユークリッド アルゴリズムlcmを使用するように関数を変更することもできます。

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 では、2 つの速度の違いはかなり大きいです。

>>> %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

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! */ }
}

乗算や除算は含まれないことに注意してください。ただし、最大で約1GiBの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ほど大きくする必要はありません。

于 2013-01-13T05:28:11.557 に答える
1

k通常、 のサブセットの最小公倍数は、20 よりもはるかに小さい場合に超えます。したがって、早期に停止する必要がありますs_iLk

おそらく、挿入するだけです

if (temp >= L) {
    break;
}

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

で十分でしょう。

また、1の中に sがある場合は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 はすべての数で割り切れるので、どこでも 0 に 1 を追加する必要はありませ1 <= k < Ls_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

サイズごとに次の葉に触れるまでの距離を記録できます。次に触れた葉までの距離は、たまたま最小になる距離になり、この距離を他のすべての距離から差し引きます (距離がゼロになるたびにラップします)。

例えば:

 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 に答える