1. 程式人生 > 實用技巧 >Codeforces 1454E - Number of Simple Paths (dfs)

Codeforces 1454E - Number of Simple Paths (dfs)

分 塊

目錄

模板題

傳送門

沒有專門模板,就直接用線段樹1啦,其實都一樣

資料結構講解

說句良心話,分塊真的不難理解,甚至比樹狀陣列,線段樹更容易,只是效率偏低

一句話概括分塊:大段維護,區域性樸素

怎麼理解呢?

以模板題(區間求和,區間更新)為例,將原數列劃分成t個塊,每個塊的大小不超過根號n,我們預處理出每一個塊內的和,另外開一個add[](類似於線段樹懶標記,但沒有下傳操作),儲存每個塊內增加的值(其實這裡講的不是很清楚,等下就明白了)

預處理

變數說明:

int n , m , t;
//n,m(見題目),t:段(塊)的數量
ll a[nn] , sum[nn] , add[nn];
//a:見題目,sum:每一段(塊)內a的和,add:等下講
int L[nn] , R[nn];
//L:每一段(塊)的左邊界,R:每一段的右邊界
int pos[nn];
//pos[i]:第i個點屬於哪一個塊
void Init() {
	t = sqrt(n);//此時的t既是段的數量,又是段的長度
	for(int i = 1 ; i <= t ; i++)
		L[i] = (i - 1) * t + 1,//這裡可以自行推導一下
		R[i] = i * t;
	if(R[t] < n) {//處理剩下的點
		++t;
		L[t] = R[t - 1] + 1 , R[t] = n;
	}
	
	for(int i = 1 ; i <= t ; i++)
		for(int j = L[i] ; j <= R[i] ; j++)
			pos[j] = i , sum[i] += a[j];
}

區間修改

對於1操作(區間修改):[l,r]上的每一個數加上dat(變數與題目不同)

  1. 若l,r同屬一個分塊,直接暴力,將a[l~r]加上dat

  2. 否則,設l處於第p個分塊,r屬於第q個分塊,

    1. \(對於i\in[p+1,q+1],add_i+=dat\)

    2. 對於開頭,結尾不足一整段的兩部分,直接用樸素方法更新即可

看程式碼:

void change(int l , int r , ll dat) {
	int p = pos[l] , q = pos[r];
	if(p == q) {//l,r同屬一個分塊
		for(int i = l ; i <= r ; i++)//暴力
			a[i] += dat;
		sum[p] += dat * (r - l + 1);//勿忘更新sum
		return;
	}
	for(int i = l ; i <= R[p] ; i++)//對於開頭不足一段的部分,暴力
		a[i] += dat;
	sum[p] += (R[p] - l + 1) * dat;
	
	for(int i = p + 1 ; i <= q - 1 ; i++)//中間,加到add即可,注意不需要更新sum,更具體的原因,查詢部分會知道
		add[i] += dat;
	
	for(int i = L[q] ; i <= r ; i++)//結尾不足一段,暴力
		a[i] += dat;
	sum[q] += (r - L[q] + 1) * dat;
}

到此,你應該理解add陣列的用處了

區間查詢

基本原理同區間修改,也是頭尾不足一段的直接暴力,中間整段查詢,這裡不再贅述

ll query(int l , int r) {
	int p = pos[l] , q = pos[r];
	ll ans = 0;
	if(p == q) {
		for(int i = l ; i <= r ; i++)
			ans += a[i];
		ans += add[p] * (r - l + 1);
		return ans;
	} 
	for(int i = l ; i <= R[p] ; i++)
		ans += a[i];
	ans += add[p] * (R[p] - l + 1);
	
	for(int i = p + 1 ; i <= q - 1 ; i++)
		ans += sum[i] + add[i] * (R[i] - L[i] + 1);
	
	for(int i = L[q] ; i <= r ; i++)
		ans += a[i];
	ans += add[q] * (r - L[q] + 1);
	return ans;
}

模板題程式碼

#include <iostream>
#include <cstdio>
#include <cmath>
#define nn 100010
#define ll long long
using namespace std;
ll read() {
	ll re = 0;
	bool sig = false;
	char c;
	do
		if((c = getchar()) == '-')sig = true;
	while(c < '0' || c > '9');
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return sig ? -re : re;
}
int n , m , t;

ll a[nn] , sum[nn] , add[nn];
int L[nn] , R[nn];
int pos[nn];

void Init() {
	t = sqrt(n);
	for(int i = 1 ; i <= t ; i++)
		L[i] = (i - 1) * t + 1,
		R[i] = i * t;
	if(R[t] < n) {
		++t;
		L[t] = R[t - 1] + 1 , R[t] = n;
	}
	
	for(int i = 1 ; i <= t ; i++)
		for(int j = L[i] ; j <= R[i] ; j++)
			pos[j] = i , sum[i] += a[j];
}
void change(int l , int r , ll dat) {
	int p = pos[l] , q = pos[r];
	if(p == q) {
		for(int i = l ; i <= r ; i++)
			a[i] += dat;
		sum[p] += dat * (r - l + 1);
		return;
	}
	for(int i = l ; i <= R[p] ; i++)
		a[i] += dat;
	sum[p] += (R[p] - l + 1) * dat;
	
	for(int i = p + 1 ; i <= q - 1 ; i++)
		add[i] += dat;
	
	for(int i = L[q] ; i <= r ; i++)
		a[i] += dat;
	sum[q] += (r - L[q] + 1) * dat;
}
ll query(int l , int r) {
	int p = pos[l] , q = pos[r];
	ll ans = 0;
	if(p == q) {
		for(int i = l ; i <= r ; i++)
			ans += a[i];
		ans += add[p] * (r - l + 1);
		return ans;
	} 
	for(int i = l ; i <= R[p] ; i++)
		ans += a[i];
	ans += add[p] * (R[p] - l + 1);
	
	for(int i = p + 1 ; i <= q - 1 ; i++)
		ans += sum[i] + add[i] * (R[i] - L[i] + 1);
	
	for(int i = L[q] ; i <= r ; i++)
		ans += a[i];
	ans += add[q] * (r - L[q] + 1);
	return ans;
}
int main() {
	n = read();	m = read();
	for(int i = 1 ; i <= n ; i++)
		a[i] = read();
	Init();
	for(int i = 1 ; i <= m ; i++) {
		int l , r , dat;
		if(read() == 1) {
			l = read(),	r = read(), dat = read();
			change(l , r , dat);
		}
		else {
			l = read(),	r = read();
			printf("%lld\n" , query(l , r));
		}
	}
	return 0;
}