線性複雜度優化小技巧

五個小技巧node

a爲原數組,從第一項開始數組

前綴和

前綴和顧名思義,前面的和,具體來講就是前n項的和
sum爲前綴和數組spa

一維前綴和

第n項的前綴和等於第n-1項前綴和+第i項數之和
\(\sum_{1}^{n}a[i]=\sum_{1}^{n-1}a[i]+a[i]\)指針

sum[i]=sum[i-1]+a[i];

應用
求靜態區間和
例如求 a[i]區間[l,r]的和
\(\sum_{i=l}^{r}a[i]=\sum_{i=1}^{r}a[i]-\sum_{i=1}^{l-1}a[i]\)code

二維前綴和

\(\sum_{i=1}^{n}\sum_{j=1}^{n}a[i][j]=\sum_{i=1}^{n-1}\sum_{j=1}^{n}a[i][j]+\sum_{i=1}^{n}\sum_{j=1}^{n-1}a[i][j]+a[n][n]-\sum_{i=1}^{n-1}\sum_{j=1}^{n-1}a[i][j]\)
(至於怎麼推出來的,建議自行畫個圖表示一下)blog

sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]

應用與一維相似排序

差分

差分 爲 相差的數隊列

一維差分

定義cf[n]爲差分序列
\(cf[i]=a[i]-a[i-1]\)
應用
區間求加數後的第i個數
例如一個區間[l,r]都加上一個數x,求第i個數
求第n個數,設第i個數爲y
\(y=\sum_{i=1}^{n}cf[i]\)ip

  • 證實
    (設a[0]=0,不影響結果
    \(cf[n]=a[n]-a[n-1]\)
    \(cf[n-1]=a[n-1]-a[n-2]\)
    \(..........\)
    \(cf[2]=a[2]-a[1]\)
    \(cf[1]=a[1]-a[0]\)
    各式相加
    \(\sum_{i=1}^{n}cf[i]=a[n]\)
    證畢

當[l,r]加上一個數x,cf[l+1,r]都不會發生改變
只有\(cf[l]=a[l]+x-a[l-1]=cf[l]+x,cf[r+1]=a[r+1]-(a[r]+x)=cf[r+1]-x\)get

二維差分

\(a[i]=sum[i]-sum[i-1]\)
\(cf[i]=a[i]-a[i-1]\)
這裏借鑑一維的變化
推出二維的差分
(爲何能推出來,由於前綴和的差分就是原數列,又由於差分的前綴和是原數列,因此由前綴和差分獲得的原序列一樣適用於由原序列差分獲得差分序列)
\(a[i][j]=sum[i][j]-sum[i-1][j]-sum[i][j-1]+sum[i-1][j-1]\)
\(cf[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]\)

應用與一維相似
至於與一位的差別
好比給以\(x_1,y_1\)爲左上角,\(x_2,y_2\)爲右下角的矩陣都加上x

此時cf數組就要進行以下操做

cf[x1][y1]+=x;
cf[x1][y2+1]-=x;
cf[x2+1][y1]-=x;
cf[x2+1][y2+1]+=x;

依次來看
根據前綴和性質
\(cf[x1][y1]+=x\) 整個黃色部分在求其a[i][j]\(i>x_1 和j>y_1\)時都會因其影響
\(cf[x1][y2+1]-=x\) 同理藍色部分會消除影響
\(cf[x2+1][y1]-=x\) 同理藍色部分會消除影響
\(cf[x2+1][y2+1]+=x\) 消除兩個藍色過分消除的影響

總結下前綴和和差分,兩個爲互逆的運算

離散化(李三花

離散化是在考慮數與數之間的大小關係而不考慮具體數值的狀況下使用

好比序列 \(1,100000,2999\)
有兩種離散化方式
這裏介紹一種
以值爲排序,用下標代替原來的值
容易知道 \(1,2999,100000\)
下標代替原來的值爲\(1,2,3\)

struct node{
	int val,p;	
}s[maxn];
bool cmp(node a,node b){
	return a.val<b.val; 
}
sort(s+1,s+1+n,cmp);

雙指針

雙指針不是c語言中的指針
而是用兩個變量遍歷

舉例就懂了,好比二分查找時的l,r 還有就是如今手寫快速排序時,左右指針找大於小於中間數的值

單調隊列

滑動窗口/[模板]單調隊列

思路就是兩遍跑單調隊列,一遍維護單調單調遞增序列,一遍維護單調遞減序列

重點介紹怎麼維護單調隊列

單調隊列,是在隊列的基礎上,使隊列一樣具備單調性(注意區分單調隊列和優先隊列)這個單調性可根據本身定義來(最小值啊,最大值啊,其餘...

  • 模擬一遍就懂了
    窗口長度爲3
    1 3 -1 -3 5 3 6 7
    這遍取最小值(至關於維護單調遞增隊列)
    q爲維護的單調隊列,p爲相對應數的下標
    1入隊
    q={1},p={1}
    3入隊
    由於3比1大且下標更大,因此有可能在滑動窗口後能爲最小值
    q={1,3},p={1,2}
    -1入隊
    由於-1比1,3都小,能夠擠掉前面的數,且下標更大
    q={-1},p={3}
    此時區間長度已爲3,下一次就會滑動窗口,因此如今輸出隊中最小值,即爲-1
    接下來每次都會滑動窗口使一個新數入列
    -3入隊,同上擠掉-1
    q={-3},p={4}
    按照上述描述
    依次隊列的變化
    q={-3,5},p={4,5}
    q={-3,3},p={4,6}
    最小值任然爲-3,但下一窗口滑動就不能取-3,此時就根據下標不在窗口內彈出-3
    因此下次隊列
    q={3,6},p={6,7}
    依次
    q={3,6,7},p={6,7,8}
    q={6,7},p={7,8}
    q={7},p={8}
    over

*code

#include<cstdio>
using namespace std;

int n,k;
int a[1000000+10];
int q[1000000+10],p[1000000+10];
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int head=1,tail=0;
	for(int i=1;i<=n;i++)
	{
		while(head<=tail && q[tail]>=a[i]) --tail;//找到比他小的數爲止 
		q[++tail]=a[i];//不然入隊列 
		p[tail]=i;
		while(p[head]<=i-k) head++;//窗口後移一次,第一個元素過期  下標比較 
		if(i>=k) printf("%d ",q[head]); //i大於窗口長度時循環一次輸出一次 輸出隊列最小值 
	} 
	printf("\n");
	head=1,tail=0;//重置隊列,再次從新從頭找最大值 
	for(int i=1;i<=n;i++)
	{
		while(head<=tail && q[tail]<=a[i]) tail--;//找到比他大的數爲止 
		q[++tail]=a[i];//不然入隊列 
		p[tail]=i;
		while(p[head]<=i-k) head++;//窗口後移一次,第一個元素過期 下標比較 
		if(i>=k) printf("%d ",q[head]); //i大於窗口長度時循環一次輸出一次 輸出隊列最大值 
	} 
}
相關文章
相關標籤/搜索