前綴和與差分(初學推薦)

前綴和

適用於靜態數組區間和
時間複雜度:O(n)
原理:當兩個整數a,b對k具體相同餘數(a%k==b%k),那麼a - b一定爲k的倍數。( a != b )
一維前綴和
題目一:k倍區間

給定一個長度爲N的數列,A1, A2, ... AN,
如果其中一段連續的子序列Ai, Ai+1, ... Aj(i <= j)之和是K的倍數,
我們就稱這個區間[i, j]是K倍區間。  
你能求出數列中總共有多少個K倍區間嗎?  
輸入
第一行包含兩個整數N和K。(1 <= N, K <= 100000)  
以下N行每行包含一個整數Ai。(1 <= Ai <= 100000)  
輸出
輸出一個整數,代表K倍區間的數目。  
例如,
輸入:
5 2
1  
2  
3  
4  
5  
程序應該輸出:
6

簡要分析:
當前綴和sum1%k=3、sum2%k=3(sum1>sum2)
它們重合的區間和:sum1-sum2%k=0,爲k的倍數。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll N, K, ans=0;
ll sum[100010];
ll cnt[100010];

int main()
{
	cin>>N>>K;

	memset(cnt, 0, sizeof(cnt));
	sum[0]=0;
	for(int i=1; i<=N; i++)
	{
		int a;
		cin>>a;
		//前綴和 
		sum[i] = (sum[i-1] + a)%K;
		cnt[sum[i]]++;
	}
	
	//情況一
	ans += (cnt[0]*(cnt[0]+1))/2; 
	
	//情況二 
	for(int i=1; i<K; i++)
		ans += (cnt[i]*(cnt[i]-1))/2;
	cout<<ans;
	
	return 0;
}

題目二:7倍最長區間

給你n個數,分別是a[1],a[2],...,a[n]。求一個最長的區間[x,y],
使得區間中的數(a[x],a[x+1],a[x+2],...,a[y-1],a[y])的和能被7整除。
輸出區間長度。若沒有符合要求的區間,輸出0。

題目二與題目一大致方法相同,只是求的答案不同,靈活變化即可。

#include<bits/stdc++.h>
using namespace std;
long long sum[100000];
int main()
{
	int n;
	cin>>n;
	memset(sum, 0, sizeof(sum));
	for(int i=1; i<=n; i++)
	{
		int a;
		cin>>a;
		sum[i] = (sum[i-1]+a)%7;
	}
	
	int begin[7], end[7];
	memset(begin, 0, sizeof(begin));
	memset(end, 0, sizeof(end));
	for(int i=n; i>0; i--)
		begin[sum[i]]=i;
	for(int i=1; i<=n; i++)
		end[sum[i]]=i;
	int len=0;
	for(int i=0; i<7; i++)
		len = max(len, end[i]-begin[i]);
	cout<<len;
	
	return 0;
} 

二維前綴和:尋找最大正方形

在一個n*m的只包含0和1的矩陣裏找出一個**不包含0**的最大正方形,
輸出邊長。
#include<bits/stdc++.h>
using namespace std;
int s[110][110];
int main() 
{
    int n,m; 
	cin>>n>>m;
	memset(s, 0, sizeof(s)); 
    for(int i=1; i<=n; i++) 
    for(int j=1; j<=m; j++) 
	{
        cin>>s[i][j];
        s[i][j] = s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
    }
    
    int l = 2;
    int ans = 1;
    
    //暴力枚舉不同大小的正方形 
    while(l <= min(n, m)) 
	{
		//暴力枚舉每個點作爲起始點 
        for(int i=l; i<=n; i++) 
        for(int j=l; j<=m; j++) 
		{
            if(s[i][j]-s[i][j-l]-s[i-l][j]+s[i-l][j-l] == l*l) 
                ans = max(ans, l);
        }
        l++;
    }
    cout<<ans<<endl;
    
    return 0;
}

差分

適用於多次給不同的區間賦予增量。
時間複雜度:O(n)求數組最終值
一維差分
簡要分析:相比於暴力方式將要改變的連續區間上的數據一個個處理,差分實現了連續區間快速變化的效果。
差分僅更改變化區間兩側的數值,再通過一次遞推得出全部區間最終的數值大小。
題目一:塗氣球
塗氣球

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n;
	while(1)
	{
		int qq[100010];
		memset(qq, 0, sizeof(qq));
		cin>>n;
		if(n==0) return 0;
		for(int i=1; i<=n; i++)
		{
			int a, b;
			cin>>a>>b;
			qq[a]++;
			qq[b+1]--;
		}
		for(int i=1; i<=n; i++)
		{
			qq[i]=qq[i]+qq[i-1];
			cout<<qq[i]<<" ";
		}
		cout<<endl; 
	}
	
	return 0;
}

題目二:排列計算

一個選手給出一個長度爲 n 的排列,另一個選手給出 m 個詢問,
每次詢問是一個形如 (l, r) 的數對,
查詢隊友給出的排列中第 l 個數到第 r 個數的和,
並將查詢到的這個區間和加入總分,
最後總分最高的隊伍就能獲勝。
聰明的你能不能預測出隊伍最終的得分呢?
一個排列是一個長度爲 n 的數列,
其中 1 ~ n 中的每個數都在數列中恰好出現一次。
比如 [1, 3, 2] 是一個排列,而 [2, 1, 4] 和 [1, 2, 3, 3] 不是排列。 
輸入描述:
第一行輸入兩個數 n (1≤n≤2×10^5^) 和 m (1≤m≤2×10^5^) 。 
接下來 m 行,每行輸入兩個數 l 和 r ,
代表這次查詢排列中第 l 個到第 r 個的和。
輸出描述:
輸出一個整數,代表他們隊伍總分的最大值。
示例1
輸入
7 3
1 3
3 7
5 6
輸出
46
說明
一個符合條件的排列是 [1,3, 6, 4, 7, 5, 2],
於是最終的得分爲 (1 + 3 + 6) + (6 + 4 + 7 + 5 + 2) + (7 + 5) = 46

簡要解析:統計每個位置出現的次數,將出現次數越多的位置記爲越大的數,最終結果就是最大值。

#include<bits/stdc++.h>
using namespace std;
int count_sz[200010];
long long ans=0;
int main()
{
	int n, m;
	cin>>n>>m;
	memset(count_sz, 0, sizeof(count_sz));
	for(int i=0; i<m; i++)
	{
		int l, r;
		cin>>l>>r;
		count_sz[l]++;
		count_sz[r+1]--;
	}
	for(int i=1; i<=n; i++)
		count_sz[i]=count_sz[i-1]+count_sz[i];
		
	sort(count_sz+1, count_sz+n+1);
	
	for(int i=1; i<=n; i++)
		ans += count_sz[i]*i;
	cout<<ans;
	
	return 0;
}

二維差分
舉例分析:
初始化
①、創建差分數組
將大小爲sum[5][4]二維數組的左下角爲[1,1]右上角爲[3,3]構成的區間增加10,創建上圖差分二維數組
差分數組設初值
②、賦值後影響區間
綠色區間:[1,1]影響的區間
藍色區間:[1,1]與[1,4]共同影響的區間
黃色區間:[1,1]與[4,1]共同影響的區間
棕色區間:[1,1]、[4,1]、[1,4]共同影響的區間
最終差分數組
③、遞推式
s[i][j]=s[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1]
通過上面的遞推式,遞推出最終的差分數組。
將最終得到的差分數組上的元素分別與二維數組中下標對應的元素相加,得到最終變化後的二維數組。
題目:鋪地毯
鋪地毯

#include<bits/stdc++.h>
using namespace std;
int dt[1010][1010];
int main()
{
	int n, m;
	memset(dt, 0, sizeof(dt));
	cin>>n>>m;
	for(int i=0; i<m; i++)
	{
		int x1, y1, x2, y2;
		cin>>x1>>y1>>x2>>y2;
		dt[x1][y1]++;
		dt[x2+1][y2+1]++;
		dt[x2+1][y1]--;
		dt[x1][y2+1]--;
	}
	
	for(int i=1; i<=n; i++)
	for(int j=1; j<=n; j++)
	dt[i][j] = dt[i][j]+dt[i][j-1]+dt[i-1][j]-dt[i-1][j-1];
	
	for(int i=1; i<=n; i++)
	{
		for(int j=1; j<=n; j++)
		cout<<dt[i][j]<<" ";
		cout<<endl;
	}
	
	return 0;
}

題目參考文章
希望將自己的學習經驗分享給有需要的人。
我是小鄭,一個堅持不懈的小白

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章