1. 程式人生 > >淺談分塊——入門

淺談分塊——入門

前言

  在學會分塊之前,覺得分塊是一個很深奧的東西,很玄學。但其實分塊的作用也很廣泛,也非常簡單,在這裡分享一下。

分塊的定義與分塊的基本性質

  分塊,顧名思義,就是將一個數組分成一些小塊。

       而分塊有一個基本性質,就是塊的大小不會影響答案,只對時間有一定影響。

       一般有以下三種分塊方式:

       No.1: 固定長度分塊。

       No.2 : 長度為sqrt(n)

       No.3 : 每塊隨機長度。

      常用的話一般選擇No.1或No.2。

分塊

    如果選擇No.2的分塊方式的話,我們定義以下幾個變數:

        

1 block = (int)sqrt(n) //塊的長度
2 
3 sum = n / block;
4 if (n % block) sum++;//塊數
5 
6 p[i] = (i - 1) / block + 1;//第i個元素在第幾塊中

  這幾個變數十分有用,在後文中會講到。

       以上是分塊的初始化。

       下面我們來講對於查詢怎麼對付,

       對於區間查詢,有三種情況:

       1:詢問區間[x,y]中x不是塊的左邊界,y也不是塊的右邊界。

       2:詢問區間[x,y]中x不是塊的左邊界,y也是塊的右邊界。

       3:詢問區間[x,y]中x是塊的左邊界,y也不是塊的右邊界。

       我們發現,對於比較普通的1情況可以把詢問區間分成3個部分:

       左邊多出來的、中間的k塊、右邊多出來的。

       可以發現,對於一塊來說查詢是O(1)的(因為我可以預處理每一塊的值)

       對於多出來的邊角料部分總和不足2*sqrt(n),基於這個時間複雜度,我們可以進行暴力求解。

       所以可以發現對於1操作查詢的總複雜度(一共有n次操作)為O(n*sqrt(n))。

       對於2、3情況,我們可以將這些情況當成1情況來處理。

       程式碼展示:

       

 1 for (int i = x; i <= min(y,p[x] * block); i++)//處理左邊角料
 2 //do something
 3 
 4  for (int i = (p[y] - 1) * block + 1; i <= y; i++)//處理右邊角料
 5  //do something
 6 
 7 for(int i = p[x] + 1; i <= p[y] - 1; i++)//處理中間k塊
 8  //do something
 9 
10 //這裡特別注意一下,對於一個塊的左邊界,我們可以求出上一個塊的右邊界(p[x] - 1)*block,+1後就是這個塊的左邊界。

      現在我們來講一下修改操作。

      跟查詢操作一樣,我們也可以分成3部分,對於邊角料暴力修改a[i],對於一塊,我們修改這一塊的值A[p[i]]

      總時間複雜度為O(n*sqrt(n))。

      程式碼展示:

      

for (int i = x; i <= min(y,p[x] * block); i++)//處理左邊角料
  change(a[i]);

for (int i = (p[y] - 1) * block + 1; i <= y; i++)//處理右邊角料
  change(a[i])

for(int i = p[x] + 1; i <= p[y] - 1; i++)//處理中間k塊
  change(A[i]);//這裡的i已經是塊號了,所以修改的是A[i],與講解的不太一樣

      對於預處理,我們發現即使每個塊暴力進行對於A[i]的預處理,時間複雜度也只有O(n),

      所以預處理就塊內暴力求解。

      程式碼展示:

      

void work(int x,int y,int t)
{
    int st = 0;
    for (int i = x; i <= y; i++)
         //do something with st
     A[t] = st;
}

for (int i = 1; i <= sum; i++)//一共sum塊
  work((p[i] - 1) * block + 1,p[i] * block,i);//傳入塊的左右邊界和編號

幾種常用的(入門)模板

  1:區間加法,單點求和。(loj6277)

   預處理時求得每個塊內的和,修改時對於邊角料暴力修改值。

    對於中間的k塊,修改加法標記addtag[i],表示第i個塊加上了addtag[i]。

    對於詢問,我們要記下對於一個塊一共加上了多少(記在addtag裡),最後輸出a[i] + addtag[p[i]]

    完整程式碼展示:

    

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 int block,sum,Ans[50005],p[50005],n,m,Add[50005];
 5 int a[50005];
 6 inline void work(int as,int bs,int x)
 7 {
 8     int st = 0;
 9     for (int i = as; i <= bs; i++)
10       st += a[i];
11     Ans[x] = st;
12 }
13 inline int read(){
14    int s=0,w=1;
15    char ch=getchar();
16    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
17    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
18    return s*w;
19 }
20 signed main(){
21     scanf("%lld",&n);
22     block = (int)sqrt(n);
23     sum = n / block;
24     if (n % block) sum++;
25     for (int i = 1; i <= n; i++)
26     {
27         //scanf("%lld",&a[i]);
28         a[i] = read();
29         p[i] = (i - 1) / block + 1;
30     }
31     for (int i = 1; i <= sum; i++)
32        work((i - 1) * block + 1,i * block,i);//預處理塊內和
33     for (int i = 1; i <= n; i++)
34     {
35         int x,y,st = 0,op,k,mp,io;
36         op = read();
37         if (op == 1)
38         {
39           mp = read();
40           x = read();
41           io = read();
42           printf("%lld\n",a[x] + Add[p[x]]);//輸出對於a[i]加上了多少
43        } else
44        {
45              x = read();
46              y = read();
47              k = read();
48              for (int j = x; j <= min(p[x] * block,y); j++)
49                a[j] += k;
50              if (p[x] != p[y])
51                for (int j = (p[y] - 1) * block + 1; j <= y; j++)
52                  a[j] += k;
53            for (int j = p[x] + 1; j <= p[y] - 1; j++)
54              Add[j] += k;//這裡直接修改加法標記
55        }
56     }
57     return 0;
58 } 

 

2:區間加法,區間求和。(loj6280)

 

 

  預處理時求得每個塊內的和,修改時對於邊角料暴力修改值,修改了a[i]的值,也要把a[i]所在塊的值修改。

    對於中間的k塊,修改加法標記addtag[i],表示第i個塊加上了addtag[i]。

    對於詢問,我們要記下對於一個塊一共加上了多少(記在addtag裡),對於邊角料同上一個模板一樣,暴力+a[i]+addtag[p[i]]

    對於中間的k塊加上A[i],因為其中有block個數,所以要+block個addtag[i]

    完整程式碼展示:

    

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 const long long inf = 66666666233;
 5 int block,sum,Ans[3000005],p[3000005],n,m,Add[3000005];
 6 int a[3000005];
 7 void work(int as,int bs,int x)
 8 {
 9     int st = 0;
10     for (int i = as; i <= bs; i++)
11       st += a[i];
12     Ans[x] = st;
13 }
14 signed main(){
15     scanf("%lld",&n);
16     block = (int)sqrt(n);
17     sum = n / block;
18     if (n % block) sum++;
19     for (int i = 1; i <= n; i++)
20     {
21         scanf("%lld",&a[i]);
22         p[i] = (i - 1) / block + 1;
23     }
24     for (int i = 1; i <= sum; i++)
25        work((i - 1) * block + 1,i * block,i);
26     for (int i = 1; i <= n; i++)
27     {
28         int x,y,st = 0,op,k;
29         scanf("%lld",&op);
30         if (op == 1)
31         {
32           scanf("%lld%lld%lld",&x,&y,&k);
33           for (int j = x; j <= y; j)
34           if ((j - 1) % block || (p[j] * block > y))
35             {     
36                 st +=  Add[p[j]] + a[j];
37                 j++;
38           } else
39           {
40               st += Ans[p[j]] + Add[p[j]] * ((p[j] * block) - ((p[j] - 1) * block + 1) + 1);//加上block個Add[p[j]]
41               j += block;
42           }
43           printf("%lld\n",st % (k + 1));
44        } else
45        {
46              scanf("%lld%lld%lld",&x,&y,&k);
47              for (int j = x; j <= y; j)
48           if ((j - 1) % block || (p[j] * block > y))
49             {     
50                 a[j] += k;
51                 Ans[p[j]] += k;
52                 j++;
53           } else
54           {      
55               Add[p[j]] += k;    
56               j += block;
57           }
58        }
59     }
60     return 0;
61 } 

3:區間開方,區間求和。(loj6281)

  預處理時求得每個塊內的和,修改時對於邊角料暴力修改值,修改了a[i]的值,也要把a[i]所在塊的值修改。

    對於中間的k塊,修改總和A[i],表示第i個塊減少了。

    對於詢問,我們要記下對於一個塊一共加上了多少(記在addtag裡),邊角料暴力+a[i]

    對於中間的k塊加上A[i],輸出答案。

    完整程式碼展示:

    

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 int block,sum,Ans[50005],p[50005],n,m,Add[50005],tag[500005];
 5 int a[50005];
 6 inline void work(int as,int bs,int x)
 7 {
 8     int st = 0;
 9     for (int i = as; i <= bs; i++)
10       st += a[i];
11     Add[x] += st;
12     //COp[x] = st;
13 }
14 inline int read(){
15    int s=0,w=1;
16    char ch=getchar();
17    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
18    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
19    return s*w;
20 }
21 void push_down(int x){
22     Add[x]=0;
23     int flag=1;
24     for(int i=(x-1)*block+1;i<=min(n,x*block);i++){
25         a[i]=(int)sqrt(a[i]*1.0);
26         Add[x]+=a[i];
27         if(a[i]!=1) flag=0;
28     }
29     tag[x]=flag;
30 }//這裡注意一下,對於中間的塊要標記下傳,重新更新
31 signed main(){
32     scanf("%lld",&n);
33     block = (int)sqrt(n);
34     sum = n / block;
35     if (n % block) sum++;
36     for (int i = 1; i <= n; i++)
37     {
38         a[i] = read();
39         p[i] = (i - 1) / block + 1;
40     }
41     for (int i = 1; i <= sum; i++)
42        work((i - 1) * block + 1,i * block,i);
43     for (int i = 1; i <= n; i++)
44     {
45         int x,y,st = 0,op,k,mp,io;
46         op = read();
47         if (op == 1)
48         {
49           x = read();
50           y = read();
51           mp = read();
52           for (int j = x; j <= min(p[x] * block,y); j++)
53                st += a[j];
54              if (p[x] != p[y])
55              for (int j = (p[y] - 1) * block + 1; j <= y; j++)
56                 st += a[j];
57           for (int j = p[x] + 1; j <= p[y] - 1; j++)
58              st += Add[j];
59           printf("%d\n",st);
60        } else
61        {
62              x = read();
63              y = read();
64              k = read();
65              for (int j = x; j <= min(p[x] * block,y); j++)
66              {
67                  int uy = a[j];
68                a[j] = (int)sqrt(a[j]);
69                Add[p[j]] -= uy - a[j];//開方就等於減少了sqrt(a[i])
70          }
71              if (p[x] != p[y])
72                for (int j = (p[y] - 1) * block + 1; j <= y; j++)
73                   {
74                       int uy = a[j];
75                     a[j] = (int)sqrt(a[j]);
76                        Add[p[j]] -= uy - a[j];
77                 }
78            for (int j = p[x] + 1; j <= p[y] - 1; j++)
79            if (tag[j] == 0) push_down(j);
80       }
81     }
82     return 0;
83 } 

4:區間加法,區間詢問小於c2的數。(loj6278、luoguP4145)

  預處理時對於所有塊進行快內排序,將未排序的a陣列賦值給tlps,修改時對於邊角料暴力修改值,修改tlps[i]的值(如果修改的是a的話,因為排過序,會發現位置不對)。

    對於中間的k塊,修改A[i]。

    對於詢問,邊角料暴力判斷

    對於中間的k塊(對於a陣列)lower_bound c^2 - addtag(因為詢問所有a + addtag 等價於 c^2 - addtag),得出這個位置,可以發現,這個位置的前面都是符合要求的。

    完整程式碼展示:

    

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 int block,sum,Ans[50005],p[50005],n,m,Add[50005];
 5 int a[50005],tlps[50005];
 6 inline void work(int as,int bs)
 7 {
 8     for (int i = as; i <= bs; i++)
 9          a[i] = tlps[i];
10     sort(a + as,a + bs + 1); 
11 }
12 signed main(){
13     //freopen("a1.in","r",stdin);
14     //freopen("ate.out","w",stdout);
15     memset(Add,0,sizeof(Add));
16     scanf("%lld",&n);
17     block = (int)sqrt(n);
18     sum = n / block;
19     if (n % block) sum++;
20     for (int i = 1; i <= n; i++)
21     {
22         scanf("%lld",&a[i]);
23         tlps[i] = a[i];//注意要複製一份
24         p[i] = (i - 1) / block + 1;
25     }
26     for (int i = 1; i <= sum; i++)
27        work((i - 1) * block + 1,min(i * block,n));
28     for (int i = 1; i <= n; i++)
29     {
30         int x,y,op,k;
31         scanf("%lld",&op);
32         if (op == 1)
33         {
34           scanf("%lld%lld%lld",&x,&y,&k);
35              k = k * k; 
36              int v = 0;
37              for (int j = x; j <= min(p[x] * block,y); j++)
38              if (tlps[j] + Add[p[x]] < k)
39                v++;
40              if (p[x] != p[y])
41                for (int j = (p[y] - 1) * block + 1; j <= y; j++)
42                  if (tlps[j] + Add[p[y]] < k)
43                    v++;
44           for (int j = p[x] + 1; j <= p[y] - 1; j++)
45             v += lower_bound(a + (j - 1) * block + 1,min(a + n,a + j * block + 1),k - Add[j]) - (a + (j - 1) * block + 1);//二分查詢
46           printf("%lld\n",v); 
47         } else
48         {
49              scanf("%lld%lld%lld",&x,&y,&k);
50              for (int j = x; j <= min(p[x] * block,y); j++)
51           {
52                tlps[j] += k;//修改的是原陣列
53             //a[j] += k;
54           }
55              work((p[x] - 1) * block + 1,min(n,p[x] * block));
56              if (p[x] != p[y])
57              {
58                for (int j = (p[y] - 1) * block + 1; j <= y; j++)
59             {
60                  tlps[j] += k;
61               //a[j] += k;
62             }
63                work((p[y] - 1) * block + 1,min(n,p[y] * block));
64           }
65           for (int j = p[x] + 1; j <= p[y] - 1; j++)
66             Add[j] += k;
67         } 
68     }
69     return 0;
70 }