信息學競賽中的一些經典思維 (題)


倍增

倍增字面上意思是:成倍地增加。當模擬一個過程時,一步一步進行太慢,考慮把模擬的步數二進制分解;經過一些預處理,每次可以模擬 \(2^i\) 步,從而達到優化復雜度的目的。
倍增主要模型有RMQ,LCA等。

例題

給出一個長度為 n 的環和一個常數 k,每次可以從第 i 個點跳到第 (i + k) mod (n+1) 個點,總共跳 m 次。第 i 個點的權值為 a[i],求 m 次跳躍的起點的權值之和 mod 1e9 + 7 。
數據范圍:$ 1 ≤ n ≤ 10^6 , 1 ≤ m ≤ 10^{18} , 1 ≤ k ≤ n , 0 ≤ a[i] ≤ 10^9 $

問題分析
這里顯然不能暴力模擬跳 m 次。因為 最大可到 \(10^{18}\) 級別,如果暴力模擬的話,時間承受不住。
所以就需要進行一些預處理,提前整合一些信息,以便於在查詢的時候更快得出結果。如果記錄下來每一個可能的跳躍次數的結果的話,不論是時間還是空間都難以承受。
倍增思想:每個數都可以表示成二進制的形式, 對於從每個點開始的 \(2^i\) 步,記錄一個 go[i][x] 表示第 x 個點跳 \(2^i\) 步之后的終點,而 sum[i][x] 表示第 x 個點跳 \(2^i\) 步之后能獲得的點權和。對於跳 \(2^i\) 步的信息,預處理的時候可以看作是先跳了 \(2^{i−1}\) 步,再跳了 \(2^{i−1}\) 步。
即有 \(sum[i][x] = sum[i-1][x]+sum[i-1][go[i-1][x]] ,且 go[i][x] = go[i-1][go[i-1][x]]\)
例如,從1到14的整個跳躍過程由 \(2^3 + 2^2 + 2^0\) 三步組成。也就是說,對於環上這 n 個位置,預處理出每一個位置向前跳 1, 2, 4, ... 次的位置,則必然能夠到達 m。
實現代碼

#include <bits/stdc++.h>
using namespace std;

const int mod = 1000000007;

int modadd(int a, int b) {
  if (a + b >= mod) return a + b - mod;  // 減法代替取模,加快運算
  return a + b;
}

int vi[1000005];

int go[75][1000005];  // 將數組稍微開大以避免越界,小的一維盡量定義在前面
int sum[75][1000005];

int main() {
  int n, k;
  scanf("%d%d", &n, &k);
  for (int i = 1; i <= n; ++i) {
    scanf("%d", vi + i);
  }

  for (int i = 1; i <= n; ++i) {
    go[0][i] = (i + k) % n + 1;
    sum[0][i] = vi[i];
  }

//int logn = 31 - __builtin_clz(n);  // 一個快捷的取對數的方法
  int logn = 65;
  for (int i = 1; i <= logn; ++i) {
    for (int j = 1; j <= n; ++j) {
      go[i][j] = go[i - 1][go[i - 1][j]];
      sum[i][j] = modadd(sum[i - 1][j], sum[i - 1][go[i - 1][j]]);
    }
  }

  long long m;
  scanf("%lld", &m);

  int ans = 0;
  int curx = 1;
  for (int i = 0; m; ++i) {
    if (m & (1 << i)) {  // 參見位運算的相關內容,意為 m 的第 i 位是否為 1
      ans = modadd(ans, sum[i][curx]);
      curx = go[i][curx];
      m ^= 1ll << i;  // 將第 i 位置零
    }
  }

  printf("%d\n", ans);
}

這題的 \(m≤10^{18}\) ,雖然看似恐怖,但是實際上只需要預處理出 65 以內的 i ,就可以輕松解決,比起暴力枚舉快了很多。用行話講,這個做法的時間復雜度是預處理 O(nlogm) ,查詢每次 O(logm) 。
倍增除了作為一種獨立的思想以外,還經常被應用到各種算法里面,例如 快速冪、LCA 和 RMQ 問題 。

總結: 這就是倍增預處理出以二的整數次冪為單位的信息:
*在遞推中,如果狀態空間很大,可以通過成倍增長的方式,只遞推出狀態空間在2的整數次冪的值作為代表。
*每個數都可以表示成二進制的形式,可用之前的求出的代表值拼成所需的值。
*要求這個遞推問題的狀態空間關於2的次冪具有可划分性。
注意:為了保證統計的時候不重不漏,一般預處理出左閉右開的點權和。

Luogu P1419 尋找段落

題目描述

給定一個長度為n的序列ai,定義a[i]為第i個元素的價值。現在需要找出序列中最有價值的“段落”。段落的定義是長度在[S,T]之間的連續序列。最有價值段落是指平均值最大的段落,段落的平均值=段落總價值/段落長度。

輸入輸出格式

輸入格式:

第一行一個整數n,表示序列長度。
第二行兩個整數S和T,表示段落長度的范圍,在[S,T]之間。
第三行到第n+2行,每行一個整數表示每個元素的價值指數。

輸出格式:

一個實數,保留3位小數,表示最優段落的平均值。

輸入輸出樣例

輸入樣例#1:

3
2 2
3
-1
2

輸出樣例#1:

1.000

數據范圍

對於30%的數據有n<=1000。
對於100%的數據有n<=100000,1<=S<=T<=n,-10000<=元素價值<=10000。

解題思路

可以看出所求問題的答案具有單調性,考慮二分答案,變為判定性問題。
對於可能的答案k,設b[i]=a[i]-k。
就是求b數組一段長度在s,t之間的和的最大值,判斷其是否大於等於0。求區間和的最大值可以用前綴和+單調隊列維護。
具體過程,貼一份洛谷題解的圖:

時間復雜度

假設數據范圍為A,則二分答案是O(logA)的,判斷一次用了前綴和和單調隊列,復雜度是O(n)的,總時間復雜度為O(nlogA)。
實現代碼

#include <bits/stdc++.h>
using namespace std;
int n,S,T,a[100005],q[100005];//q數組用來記錄前綴和的下標
double Sum[100005];           //前綴和記錄到第i個的減去平均值的和
int check(double m)
{
    Sum[0]=0;
    for(int i=1;i<=n;i++)
        Sum[i]=Sum[i-1]+a[i]-m; //初始化差值的前綴和 
 
    int head=1,tail=0;	        //head為單調(上升)隊列的左端點,tail為右端點。
    for(int i=1;i<=n;i++)
    {
        if(i>=S)
        {
            //一直減小單調隊列的右端點,隊列右端點處的位置其前綴和小於Sum[i-S]
            //單調隊列 q[] 中存的是,假設區間的右端點是i,區間前面的左端點最小可行位置 i-S 
            while(head<=tail&&Sum[i-S]<Sum[q[tail]])
                tail--;     //維護隊列的右端點
            q[++tail]=i-S;  //對於每一個右邊的i,都將它的區間左端點初始化為 i-S;
        }
        //單調隊列的隊首存儲的區間左端點位置,小於滿足區間長度[S,T]范圍時,i位置的左端點最小值;
        if(head<=tail&&i-q[head]>T)	
            head++;
        //如果這一段區間內的和大於等於0,則證明有這個值可行,且可能由更大的平均值存在。
        if(head<=tail&&Sum[i]-Sum[q[head]]>=0)
            return 1;
    }
    return 0;
}
int main()
{
    cin>>n>>S>>T;
    for(int i=1;i<=n;++i)
    {
        cin>>a[i];
    }
    double l=-10005,m,r=10005;
    while(l+1e-5<r)  // 二分區間平均值的最大值,注意控制精度保留三位小數 
    {
        m=(l+r)/2;
        if(check(m))
            l=m;
        else
            r=m;
    }
    cout <<fixed<<setprecision(3)<<l<< endl;
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM