3

さて、私はこのCodechef でのコインのフリッピングの問題を解決しようとしていました。セグメントツリーで解決しています。しかし、制限時間を超えています。検索したところ、遅延伝播を使用する必要があることがわかりました。しかし、私には理解できません。私の更新機能は再帰的に(上から下へ)動作します。ヒントを与えるか、例を挙げて説明してください。また、コードを変更する必要がある場所も指摘してください。

コイン投げでは、更新時にノード値が 1 の場合は 0 に、0 の場合は 1 に変更します。

start と end は、元の配列の制限です。tree はセグメント ツリー配列です。

void update(int node, int start, int end,int pos)//pos=position to update
{
    if(start==end) tree[node]=(tree[node]==0) ? 1 : 0;
    else
    {
        int mid=(start+end)/2;
        if(mid>=pos) update(2*node + 1, start, mid, pos);
        else update(2*node + 2, mid+1, end, pos);

        tree[node]=tree[2*node +1] + tree[2*node +2];
    }
}
4

3 に答える 3

9

遅延伝播とは、必要な場合にのみ更新することを意味します。これは、範囲の更新を漸近的な時間の複雑さ O(logN) (ここでの N は範囲) で実行できるようにする手法です。

範囲 [0,15] を更新したい場合は、ノード [0,15] を更新し、子ノードが更新されることを示すフラグをノードに設定します(フラグが更新されない場合は、センチネル値を使用します)。中古)

可能なストレス テスト ケース:

0 1 100000

0 1 100000

0 1 100000 ... Q 回 (Q = 99999) を繰り返すと、100000 番目のクエリは次のようになります。

1 1 100000

その場合、ほとんどの実装は 100000 コインを 99999 回投げて、最後に 1 つの単純なクエリに応答してタイムアウトします。

遅延伝播では、ノード [0,100000] を 99999 回反転し、その子が更新されるフラグを設定/設定解除するだけです。実際のクエリ自体が要求されると、その子のトラバースを開始して反転を開始し、フラグを押し下げて親のフラグを設定解除します。

ああ、適切な I/O ルーチンを使用していることを確認してください (C++ の場合は cin と cout の代わりに scanf と printf)。詳細: http://www.spoj.pl/forum/viewtopic.php?f=27&t=8296

于 2012-05-24T10:05:43.210 に答える
2

遅延更新操作と通常の更新操作を対比し、これがクエリ操作をどのように変更するかを説明します。

通常の単一の更新操作では、ツリーのルートを更新してから、ツリーの必要な部分のみを再帰的に更新します (したがってO(log(n))速度が向上します)。範囲の更新に同じロジックを使用しようとすると、どのように悪化するかがわかりますO(n)(非常に広い範囲を考慮して、ツリーの両方の部分を更新する必要があることがほとんどです)。

したがって、この考えを克服するにO(n)は、本当に必要な場合にのみツリーを更新することです (以前に更新されたセグメントに対してクエリ/更新を行い、更新を遅延させます)。したがって、これがどのように機能するかです:

  • ツリーの作成はまったく同じままです。唯一の小さな違いは、潜在的な更新に関する情報を保持する配列も作成することです。
  • ツリーのノードを更新するときは、(前回の更新操作から) 更新する必要があるかどうかも確認し、更新する必要がある場合は、更新し、子を将来更新するようにマークし、ノードのマークを解除します (遅延)。
  • ツリーをクエリするときは、ノードを更新する必要があるかどうかも確認し、更新する必要がある場合は、それを子としてマークし、後でマークを解除します。

更新とクエリの例を次に示します (最大範囲クエリの解決)。完全なコードについては、この記事を確認してください。

void update_tree(int node, int a, int b, int i, int j, int value) {
    if(lazy[node] != 0) { // This node needs to be updated
        tree[node] += lazy[node]; // Update it
        if(a != b) {
            lazy[node*2] += lazy[node]; // Mark child as lazy
            lazy[node*2+1] += lazy[node]; // Mark child as lazy
        }
        lazy[node] = 0; // Reset it
    }

    if(a > b || a > j || b < i) // Current segment is not within range [i, j]
        return;

    if(a >= i && b <= j) { // Segment is fully within range
        tree[node] += value;
        if(a != b) { // Not leaf node
            lazy[node*2] += value;
            lazy[node*2+1] += value;
        }
        return;
    }

    update_tree(node*2, a, (a+b)/2, i, j, value); // Updating left child
    update_tree(1+node*2, 1+(a+b)/2, b, i, j, value); // Updating right child
    tree[node] = max(tree[node*2], tree[node*2+1]); // Updating root with max value
}

およびクエリ:

int query_tree(int node, int a, int b, int i, int j) {
    if(a > b || a > j || b < i) return -inf; // Out of range

    if(lazy[node] != 0) { // This node needs to be updated
        tree[node] += lazy[node]; // Update it
        if(a != b) {
            lazy[node*2] += lazy[node]; // Mark child as lazy
            lazy[node*2+1] += lazy[node]; // Mark child as lazy
        }
        lazy[node] = 0; // Reset it
    }

    if(a >= i && b <= j) // Current segment is totally within range [i, j]
        return tree[node];

    return max(query_tree(node*2, a, (a+b)/2, i, j), query_tree(1+node*2, 1+(a+b)/2, b, i, j));
}
于 2015-01-20T01:29:14.030 に答える
1

5..15のみを「はい」としてマークするには、以下が必要なすべてです。操作に関与するノードは7つだけであり、遅延伝播を使用しない場合よりもはるかに少なくなります。2logn - 1ほとんどのノードが関与している可能性があることを証明できます。ここnで、は範囲です。

         0..31u
         /    \
      0..15u 16..31n
      /    \
   0..8u  9..15y
   /   \
0..4n  5..8y 

(u: unknown, look deeper; y: yes; n: no)

怠惰な伝播がなければ、セグメントツリーは単純な配列よりも優れているわけではありません。

実装方法の詳細はあなたに任されています。あなたは自分でそれを解決する必要があります。

于 2012-05-24T08:40:44.023 に答える