1次元でも2次元でも問題なければ直接ジャンプできます
1Dの差
最初に元の配列 a: a[1]、a[2]、a[3]、a[n] が与えられ、次に配列 b:
b[1] 、b[2] 、b[3]、b を構築します。 [i];
a[i] = b[1] + b[2] + b[3] +, + b[i] を作成します。
a 配列は b 配列の接頭辞および配列であり、b 配列を a 配列の差分配列と呼びます。
次のように差分 b 配列を構築する
最も直接的な方法を考えてみましょう: a[0]= 0; b[1] = a[1] - a[0]; b[2] = a[2] - a[1] ; b[ 3] = a [3] - a[2]; … b[n] = a[n] - a[n-1]; b の配列があり、プレフィックスと演算により、配列の取得は O(n) 時間で完了します。
1 次元差分 - テンプレート問題 AcWing 797. 差分
[l, r] 区間の各数値に c を加算します: b[l] += c, b[r + 1] -= c 質問例 AcWing797. 差分
入力
1
A長さ n の整数のシーケンス。次に、m 個の操作を入力します。各操作には 3 つの整数 l、r、c が含まれます。これは、シーケンス内の [l, r] 間の各数値に c を加算することを意味します。
すべての操作が完了してからシーケンスを出力してください。
入力形式
最初の行には 2 つの整数 n と m が含まれます。
2 行目には、一連の整数を表す n 個の整数が含まれています。
次の m 行には、各行に演算を表す 3 つの整数 l、r、c が含まれます。
出力形式
最終シーケンスを表す n 個の整数を含む合計 1 行。
データ範囲
1≦n、m≦100000、1≦l≦
r≦n、
−1000≦c≦1000、
−1000≦整数列の要素の値≦1000
入力例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
出力例:
3 4 5 3 4 2
#include<bits/stdc+.和>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i]-a[i-1]; //构建差分数组
}
int l,r,c;
while(m--)
{
scanf("%d%d%d",&l,&r,&c);
b[l]+=c; //将序列中[l, r]之间的每个数都加上c
b[r+1]-=c;
}
for(int i=1;i<=n;i++)
{
a[i]=b[i]+a[i-1]; //前缀和运算
printf("%d ",a[i]);
}
return 0;
}
2次元差分
著者: z Lin Shen Shi Jianlu
2 次元に拡張する場合、O(1) 時間計算量も達成できるかどうか、2 次元配列の選択された部分行列の各要素の値に c を追加する必要があります。 。答えは「はい」です。2 次元の差分を考慮してください。
a[][] 配列は b[][] 配列のプレフィックスおよび配列であり、b[][] は a[][] の差分配列です 元の配列: a[i][j] を構築しましょう差分配列
:
b [i][j]
は、a 配列の a[i][j] を左上隅 (1,1) から右下隅 (i,j) で囲まれた長方形要素の合計とします。 ) b 配列の。
b 配列を構築するにはどうすればよいですか?
逆に考えてみましょう。
2 次元差分配列を構築する目的は、元の 2 次元配列 a の選択された中性子行列の各要素に c を加算する操作を行うことです。これは O(n*n) の時間計算量から最適化できます。へ(1)
元の配列 a で選択された部分行列は、左上隅の (x1, y1) と右下隅の (x2, y2) で囲まれた長方形の領域であることが知られています。
配列 a は配列 b のプレフィックスおよび配列であることに常に注意してください。たとえば、配列 b の b[i][j] を変更すると、配列 a の a[i][j] 以降のすべての数値に影響します。
1 次元差分と同様に、配列 b をすでに構築していると仮定して、次の演算を実行して、
選択した部分行列の各要素の値を時間計算量 o(1)に加算します。
b[x1][y1] + = c;
b[x1,][y2+1] - = c;
b[x2+1][y1] - = c;
b[x2+1][y2+1] + = c;
上記の演算が b 配列に対して実行されるたびに、時間計算量 o(n2)と等価になります。
for(int i=x1;i<=x2;i++)
for(int j=y1;j<=y2;j++)
a[i][j]+=c;
このプロセスを理解するために図を描いてみましょう:
b[x1][ y1 ] +=c ; 図 1 に対応して、配列 a 全体の青い四角形領域の要素に c を追加します。
b[x1,][y2+1]-=c ; 図 2 に対応して、配列 a 全体の緑色の長方形領域の要素から c を減算し、その中の要素が変わらないようにします。
b[x2+1][y1]- =c ; 図 3 に対応して、配列 a 全体の紫色の長方形の領域の要素から c を減算し、その中の要素が変わらないようにします。
b[x2+1][y2+1]+=c; 図 4 に対応して、配列 a 全体の赤い四角形領域の要素に c を追加すると、赤い要素は 2 回減算され、c が 1 回加えられることに相当します。それを復元するために。
上記の操作を挿入関数にカプセル化します。
void insert(int x1,int y1,int x2,int y2,int c)
{
//对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
まず配列 a が空であると仮定し、次に配列 b も最初は空であると仮定できますが、実際には配列 a は空ではないため、(i, j) を (i, j) の左上隅とします。毎回右上隅の領域(実際には小さな正方形の領域)の要素が c=a[i][j] に挿入されます。これは、(i からの範囲を加算するのと同じです) ,j) を元の配列 a a[i][j] の (i,j) に変換するため、n*m 個の挿入操作が実行され、差分配列 b が正常に構築されます。
これを国を救う曲線と呼びます。
コードは以下のように表示されます。
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
insert(i,j,i,j,a[i][j]); //构建差分数组
}
}
2 次元の差分 - テンプレートの質問 AcWing 798. 差分行列
(x1, y1) を左上隅、(x2, y2) を右下隅として部分行列のすべての要素に c を加算します:
S[x1, y1 ] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
質問例
AcWing 798. 差分行列
n 行 m 列の整数行列を入力し、q 個の演算を入力します。各演算には 5 つの整数 x1、y1、x2、y2、c が含まれます。ここで (x1, y1) と (x2, y2) ) は部分行列の左上と右下の座標を表します。
各演算により、選択された部分行列の各要素の値に c が加算されます。
すべての演算が終了したら行列を出力してください。
入力形式
最初の行には整数 n、m、q が含まれます。
次の n 行は、それぞれ m 個の整数を含み、整数の行列を表します。
次の q 行には、各行に演算を表す 5 つの整数 x1、y1、x2、y2、c が含まれています。
出力形式
には合計 n 行があり、各行には m 個の整数が含まれており、すべての演算が完了した後の最終行列を示します。
データ範囲
1≤n、m≤1000、1≤q≤100000、1≤x1≤x2≤n
、1≤y1≤y2≤m、−1000≤c≤1000、−1000≤行列の要素の値
≤1000入力例: 3 4 3 1 2 2 1 3 2 2 1 1 1 1 1 1 1 2 2 1 1 3 2 3 2 3 1 3 4 1出力例: 2 3 4 1 4 3 4 1 2 2 2 2
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e3 + 10;
int a[N][N], b[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main()
{
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
insert(i, j, i, j, a[i][j]); //构建差分数组
}
}
while (q--)
{
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1]; //二维前缀和
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
printf("%d ", b[i][j]);
}
printf("\n");
}
return 0;
}
3D差分
一次元を観察する
b[l] = s[l] - s[l-1]
+s[l] 、 -s[l-1]
2 次元
b[i][j] = s[i][j] - s[i-1][j] - s[i][j-1] + s[i-1][j-1]
-s[i-1][j] 、 -s[i][j-1] 、 +s[i][j] 、 +s[i-1][j-1] は、マイナス 1 の奇数になります。は
負、偶数マイナス 1 は正
0 0 { s[ i - 0 ][ j - 0 ] = s[ i ][ j ] } +1
0 1 { s[ i - 0 ][ j - 1 ] = s[ i ] [ j - 1] } -1
1 0 { s[ i - 1 ][ j - 0 ] = s[ i - 1 ][ j ] } -1 1 1 { s[ i -
1 ][ j - 1 ]} -1
元のシーケンス (x1, y1) と (x2, y2) の間に c を追加すると、次のようになります。
b[x1][y1] += c; //00
b[x1][y2+1] -= c; //01
b[x2+1][y1] -= c; //10
b[x2+1][y2+1] += c; //11
同様に
三次元でも
b[i][j][k] = s[i][j][k] - s[i-1][j][k] - s[i][j-1][k] + s[i-1][j-1][k]- s[i][j][k-1] + s[i-1][j][k-1] + s[i][j-1][k-1] - s[i-1][j-1][k-1];
元のシーケンスの (x1, y1, z1) と (x2, y2, z2) の間に c を追加します。
b[x1 ][y1 ][z1 ] += c; // 000
b[x1 ][y1 ][z2 + 1] -= c; // 001
b[x1 ][y2 + 1][z1 ] -= c; // 010
b[x1 ][y2 + 1][z2 + 1] += c; // 011
b[x2 + 1][y1 ][z1 ] -= c; // 100
b[x2 + 1][y1 ][z2 + 1] += c; // 101
b[x2 + 1][y2 + 1][z1 ] += c; // 110
b[x2 + 1][y2 + 1][z2 + 1] -= c; // 111
問題例
AcWing 1232. 三体攻撃
三体人が地球を攻撃します。
これに対抗するため、地球人は宇宙にA層、B列、C列の立方体を並べたA×B×Cの軍艦を派遣した。
このうち、層iのj行k列の戦艦(戦艦(i,j,k)とする)のライフ値はd(i,j,k)である。
トリソラランは地球上で m 回の「キューブ攻撃」を開始し、各攻撃は小さなキューブ内のすべての軍艦に同じダメージを与えます。
具体的には、t 回目の攻撃は 7 つのパラメータ lat、rat、lbt、rbt、lct、rct、ht で記述され、
] の戦艦 (i,j,k) は ht によってダメージを受けます。
戦艦が受けたダメージの累計が防御力を超えると戦艦は爆発します。
地球司令官は、最初の戦艦がどのラウンド後に爆発したかを教えてほしいとしています。
入力形式
最初の行には、4 つの正の整数 A、B、C、m が含まれます。
2 行目には A×B×C の整数が含まれており、((i−1)×B+(j−1))×C+(k−1)+1 の数値は d(i, j, k) ; 3 から行になり
ますm+2、線 (t − 2) には 7 つの正の整数 lat、rat、lbt、rbt、lct、rct、ht が含まれます。
出力形式
最初に爆発した軍艦が攻撃のどのラウンド後に爆発したかを出力します。
そのような戦艦が存在することは保証されています。
データ範囲
1≦A×B×C≦
106、1≦m≦106、0
≦d(i、j、k)、ht≦109、1≦lat≦rat≦A、1≦lbt≦
rbt
≦B、
1 ≤lct≤rct≤C
レイヤー、行、列はすべて 1 から始まります。
入力例:
2 2 2 3
1 1 1 1 1 1 1 1
1
2 1 2 1 1 1 1 1 1 2 1 2 1
1 1 1 1 1 1 2
出力例:
2 つの例で 2 ラウンド目の攻撃
を説明その後
, 戦艦(1,1,1)は合計2点のダメージを受け、防御力を超えて爆発を起こしました。
A×B×C<=1e6 の場合、3 次元配列を開くとタイムアウトになる可能性があり、対応する 3 次元座標を 1 次元座標 (i×B+j)×k に変換するとクエリが多すぎることに注意してください。
バイナリの解決策は、最初に爆撃された戦艦を見つけることです。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int const N=2e6+10;//多出一个面 (i,j,k,+1);
int a,b,c,m;
ll s[N];//原数组;
ll bb[N],dp[N]; //差分数组;
int op[N/2][7];
int d[8][4]=
{
{
0,0,0,1},{
0,0,1,-1},{
0,1,0,-1},{
0,1,1,1},
{
1,0,0,-1},{
1,0,1,1},{
1,1,0,1},{
1,1,1,-1},
};
int get(int i,int j,int k) //以一维数组的形式
{
return (i*b+j)*c+k;
}
bool check(int mid)//判断是否是第一个被炸毁的;
{
memcpy(bb,dp,sizeof bb);//拷贝;
for(int i=1; i<=mid; i++)
{
//将位于(x1, y1, z1)和(x2, y2, z2)之间的原序列都减去h:
int x1 = op[i][0], x2 = op[i][1];
int y1 = op[i][2], y2 = op[i][3];
int z1 = op[i][4], z2 = op[i][5], h = op[i][6];
bb[get(x1, y1, z1)] -= h;
bb[get(x1, y1, z2 + 1)] += h;
bb[get(x1, y2 + 1, z1)] += h;
bb[get(x1, y2 + 1, z2 + 1)] -= h;
bb[get(x2 + 1, y1, z1)] += h;
bb[get(x2 + 1, y1, z2 + 1)] -= h;
bb[get(x2 + 1, y2 + 1, z1)] -= h;
bb[get(x2 + 1, y2 + 1, z2 + 1)] += h;
}
memset(s,0,sizeof s); //前缀和求更改后原数组;
for(int i=1; i<=a; i++)
for(int j=1; j<=b; j++)
for(int k=1; k<=c; k++)
{
s[get(i,j,k)]=bb[get(i,j,k)];
for(int u=1; u<8; u++)
{
int x=i-d[u][0],y=j-d[u][1];
int z=k-d[u][2],t=d[u][3];
s[get(i,j,k)]-=s[get(x,y,z)]*t;
}
if(s[get(i,j,k)]<0)
return true;
}
return false;
}
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>a>>b>>c>>m;
for(int i=1; i<=a; i++)
for(int j=1; j<=b; j++)
for(int k=1; k<=c; k++)
cin>>s[get(i,j,k)];
//差分
for(int i=1; i<=a; i++)
for(int j=1; j<=b; j++)
for(int k=1; k<=c; k++)
for(int u=0; u<8; u++) //一共8种情况;000 001 010...
{
int x=i-d[u][0],y=j-d[u][1],z=k-d[u][2],t=d[u][3];
dp[get(i,j,k)]+=s[get(x,y,z)]*t;
}
//输入m个操作;
for(int i=1; i<=m; i++)
for(int j=0; j<7; j++)
cin>>op[i][j];
//二分查找
int l=1,r=m;
while(l<r)
{
int mid=l+r >> 1;
if(check(mid)) //如果第mid操作有炸毁,往前找
r=mid;
else
l=mid+1;
}
cout<<r<<endl;
return 0;
}