mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1000 文字
3 分
アルゴリズム学習:前計算和・差分・双方向ポインタ
2022-07-16

1. 累積和#

1.1 アルゴリズムの原理#

いわゆる累積和とは、前方のすべてのデータの和を記録しておくことを指し、所要の中間データを得る際には、o(1)の時間計算量でデータを求めることができる。

  • 一次元配列の累積和

1からiまでのすべての項の和を求める。

  1. 当運算到第i位時、前i-1位はすでに運算完了しているため、a[i] = a[i] + a[i-1]

  2. 当需要[l,r]の和時は、a[r]-a[l-1]で求められる

  • 二次元配列の累積和

左上端点、右下端点がそれぞれ(1,1)、(i,j)の長方形内のデータの和を求める

i,jへ到達した時、前i-1排目と、第i排目の前j-1位はすべて計算済みであるため、a[i][j] = a[i][j]+a[i-1][j]+a[i-1]-a[i-1][j-1]

  1. 左上端点、右下端点がそれぞれ(x1,y1)(x2,y2)の長方形内のデータの和は、a[x2][y2]-a[x2][y1-1]-a[x1-1][y2]+a[x1-1][y1-1]

1.2 例題#

参考コード:

void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1];
while(m--){
cin>>l>>r;
cout<<a[r]-a[l-1]<<endl;
}
}

参考コード:

void solve(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
cin>>a[i][j],a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
while(q--){
cin>>x1>>y1>>x2>>y2;
cout<<a[x2][y2]-a[x2][y1-1]-a[x1-1][y2]+a[x1-1][y1-1]<<endl;
}
}

2. 差分#

2.1 アルゴリズムの原理#

差分とは、各データと前のデータとの差を記録することで、区間に対して操作を行う際にo(1)の時間計算量で処理を完了できる。

また、差分配列の前縁の和は元の配列そのものになることが分かる。

  • 一次元配列の差分

前項との差を求める。 [l,r] 範囲の数を加減する必要がある場合、b[l]+=cb[r+1]-=c で実現

  • 二次元配列の差分

二次元差分配列の構築は、その前縁和の結果から逆算して考えることができる。

(x1,y1)(x2,y2) の長方形内のデータをすべて c 加えるとき、すなわち a[x1,y1]~a[x2,y2] がすべて c となるようにします。したがって b[x1,y1]+=cb[x1,y2+1]-=cb[x2+1,y1]-=cb[x2+1,y2+1]+=c とすれば、区間内のデータのみを加えることができます。

20201217171134926.png

差分配列を構築する際、それは長さ1の長方形に対して a[i,j] を加えることになるため、2次元差分配列を実現できる。

2.2 例题#

参考コード:

void solve(){
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) b[i] = a[i]-a[i-1];
while(m--){
cin>>l>>r>>c;
b[l]+=c;b[r+1]-=c;
}for(int i=1,t=0;i<=n;i++) t+=b[i],cout<<t<<' ';
}

参考コード:

void add(){
b[x1][y1] += c;b[x1][y2+1] -=c;
b[x2+1][y1] -= c;b[x2+1][y2+1] +=c;
}
void solve(){
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
cin>>a[i][j],x1=x2=i,y1=y2=j,c=a[i][j],add();
while(q--){
cin>>x1>>y1>>x2>>y2>>c;
add();
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j] = b[i][j] + a[i-1][j]+a[i][j-1]-a[i-1][j-1];
cout<<a[i][j]<<' ';
}cout<<endl;
}
}

3. 双指针#

3.1 アルゴリズムの原理#

ダブルポインタ問題は、列の性質に基づき、有効なクエリ範囲を指針 j で記録することにより、計算時間の複雑さを低減します。

ダブルポインタ問題は主に以下の2つに分類されます。

  1. 一つの列で、二つのポインタが区間を維持する。
  2. 二つの列で、ある種の順序を維持する

基本テンプレート(AcWing より)

void solve(){
for(int i=0,j=0;i<n;i++){
while(j<i && check(i,j)) j++;
//具体的な問題ロジック
}
}

3.2 例题#

解法の逻辑:cnt 配列を用いて [j,i] 区間内のデータの出現回数を記録します。ia[i] をすでに出現させている場合、[j,i-1] 内で a[i] の出現回数が0になるまで j を前方へ動かします。長さを途中で記録します。

void solve(){
for(int i=1,j=1;i<=n;i++){
if(cnt[a[i]]){
while(cnt[a[i]]) cnt[a[j++]]--;
}
cnt[a[i]]++;
ans = max(i-j+1,ans);
}cout<<ans<<endl;
}

解法の逻辑:数列は昇順なので、a[i]+a[j] > x となると、a[i+1]+a[j] は必ず x より大きくなる。従って判定が必要な境界を j で記録することで探索時間を削減できる。

void solve(){
for(int i=0,j=m-1;i<n,j>0;i++){
while(a[i]+b[j]>x) j--;
if(a[i]+b[j]==x){
cout<<i<<' '<<j<<endl;
return 0;
}
}
}

解法の逻辑:母串中に子文字列は順序通り現れるため、母串中の各位置に対応する位置を順に探すことができます。

void solve(){
for(int i=0,j=0;i<n && j<m;i++,j++){
while(a[i] != b[j] && j<m) j++;
if(i==n-1 && j < m) {cout<<"Yes"<<endl;return 0;}
}cout<<"No"<<endl;
}
共有

この記事が役に立ったときは、ぜひ他の人に共有してください!

アルゴリズム学習:前計算和・差分・双方向ポインタ
https://dreaife.tokyo/jp/posts/prefix-sum-difference/
著者
dreaife
公開日
2022-07-16
ライセンス
CC BY-NC-SA 4.0

一部の情報は古い可能性があります

関連した投稿 スマート
1
アルゴリズム学習:ビット演算・離散化・区間マージ
algorithm ビット演算、離散化、区間マージのアルゴリズムを紹介します。ビット演算は2進数処理に用いられ、離散化は疎データの保存・検索を最適化し、区間マージはソートと重なり判定によって複数区間を効率的に統合します。理解と応用を助けるため、関連する例題と参考コードも掲載しています。
2
高精度演算の学習記録
algorithm 高精度演算には加算、減算、乗算、除算が含まれ、通常のデータ型の範囲を超える大きな数を扱う際に用いられます。加算と減算は筆算のシミュレーションで実装し、乗算と除算は各桁を順に処理しつつ先頭のゼロ除去に注意します。理解と実装に役立つ例題と参考コードも掲載しています。
3
ソートと二分探索の学習
algorithm クイックソートとマージソートの原理と実装を、時間計算量や関連問題とあわせて紹介します。クイックソートは中間値を基準に数列を2つに分割して整列し、マージソートは整列済み部分列を統合していきます。整数・浮動小数点数に対する二分探索アルゴリズムとその実装方法についても解説します。
4
面接アルゴリズム学習1
algorithm 蛇行行列の埋め込み、単方向連結リストの高速ソート、ピーク値や極小値の探索、卵の硬度問題、最小値取得をサポートするスタック、連結リストの循環開始ノード探索など、複数のアルゴリズム面接問題とその解法をまとめています。各問題には詳細な説明、入出力形式、サンプルコードが付いています。
5
EOAウォレットの署名検証とその関連内容について
WEB3 この記事では、Ethereum EOA ウォレットにおける署名検証の完全な流れと、その背後にある数学的原理を詳しく解説します。まず、secp256k1 曲線の有限体 Fₚ、楕円曲線点群 E(Fₚ)、基点 G とその位数 n の基本概念を振り返り、点加算、点倍加、スカラー倍算における mod p と mod n の計算方法を詳細に説明します。続いて、実際の SIWE(Sign-In with Ethereum)シナリオを通じて、ウォレットが署名リクエストを受け取った後に r/s/v の三つ組をどのように生成するかを、ハッシュ計算、乱数 k の生成、点 R の算出、r・s・v の具体的な式を含めて段階的に示します。さらに、サーバー側が既知の r、s、v、メッセージハッシュ e、基点 G を用いて、Q = r⁻¹(sR − eG) という式から公開鍵 Q を逆算して検証し、keccak-256 の後半 20 バイトを取得してウォレットアドレスを得ることで、秘密鍵を漏えいさせずに所有権確認を実現する仕組みを説明します。また、p と n の違い、楕円曲線離散対数問題の計算困難性(約 2¹²⁸)、および現在の量子計算がこの安全性に与え得る潜在的影響についても指摘します。全体として、開発者に理論から実装までの包括的な参考情報を提供し、「EOA ウォレット署名検証」「secp256k1」「ECDSA」「SIWE」などの関連キーワードにおける検索可視性を高めるブログ SEO 摘要としても適しています。

目次