1. 程式人生 > >XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Khamovniki Problem J Stairways解題報告(分塊+維護凸殼)

XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Khamovniki Problem J Stairways解題報告(分塊+維護凸殼)

這裡寫一個更詳細的題解吧(我還是太菜了啊)。

題目描述

有\(n(n \le10^5)\)個人依次進入一個入口,要到一個出口。入口到出口有兩條同樣長的路。每個人都有一個速度,用通行時間\(a_i(1\le a_i \le 10^6)\)表示,他可以選擇任一條路走。但是,若走這條路的前面的人比他慢的話,他只能降到和前面所有人最慢的那個人同樣的速度(從而會多花時間)。現在請規劃每個人選哪條路,使得每個人因等前面的人而浪費的時間儘可能少。

Sample Input

100
4 3 2 1

Sample Output

6

詳細題解

此題很容易用DP來做。考慮前\(i\)個人,則兩個樓梯必有一個的通行時間變為前\(i\)個人最慢的那個,我們設\(dp[i][j]\)表示前i個人另一個樓梯當前通行時間是\(j\)(\(j\)從小到大離散化)時的最優答案,則考慮\(dp[i+1]\)和\(dp[i]\)的關係:

(1)若\(a[i+1]>=max(a[1..i])\),則顯然\(dp[i+1][j]=dp[i][j]\);

(2)若\(a[i+1]<max(a[1..i])\),則:

情況1:\(j\)對應狀態快的那個樓梯比\(a[i+1]\)時間短,且選這個樓梯,於是\(dp[i+1][k]=min(dp[i][j],j<=k)\),其中\(k\)為\(a[i+1]\)離散化的結果;

情況2:\(j\)對應狀態快的那個樓梯比\(a[i+1]\)時間短,但選最慢的樓梯,於是\(dp[i+1][j]=dp[i][j]+max(a[1..i])-a[i+1]\),其中\(j<k\);

情況3:\(j\)對應狀態快的那個樓梯比\(a[i+1]\)的時間長,那必然選這個樓梯,於是\(dp[i+1][j]=dp[i][j]+f[j]-a[i+1]\),其中\(j>k\),\(f[j]\)表示第\(j\)小的值。

這樣狀態數和轉移複雜度均為\(n^2\)。下面考慮資料結構優化。

我們需要維護的dp要支援區間最小值查詢,單點修改,區間增加,和區間\(dp[i][j]+=f[j]\)。

如果沒有最後的操作此題直接用線段樹就簡單多了。

加上了這種操作,考慮分塊。每塊首先要維護增量tag,該tag對最值無影響。下面主要考慮\(dp[i][j]+=f[j]\)。

注意到一個性質:若\((dp[i][j+1]-dp[i][j])/(f[j+1]-f[j])<(dp[i][j]-dp[i][j-1])/(f[j]-f[j-1])\),那麼無論再怎麼增加\(dp[i][j]\)也不可能最優。所以將\(j\)下標看做二維點\((f[j],dp[i][j])\)後,所有可能的最優值形成一個下凸殼。當整塊\(dp[i][j]+=f[j]\)後,凸殼上仍是這些點,但最小值點可能將向左移動。於是我們只要不斷刪除凸殼右邊的點,就可以每一塊均攤\(O(1)\)的修改和查詢最小值。

對於單點修改,只需要重構凸殼,複雜度為塊大小。

現在考慮分塊後從\(dp[i]\)轉移到\(dp[i+1]\)的總複雜度,設塊大小\(b\)。由於單點修改僅一個點\(k\),故複雜度\(b\);取最小值複雜度\(b+n/b\);區間加複雜度\(b+n/b\);區間\(dp[i][j]+=f[j]\)複雜度\(b+n/b\)。當\(b\)取\(\sqrt n\) 時複雜度最優,為\(\sqrt n \)。考慮到重構凸殼較慢,應在求最值時如需要再重構凸殼。

總時間複雜度\(O(n \sqrt n)\)

AC程式碼

  1 #include<cstdio>
  2 #include<cmath>
  3 #include<algorithm>
  4 #include<cstring>
  5 using namespace std;
  6 #define LL long long
  7 struct Block{
  8     LL a[400], tag, delta;
  9     int order[400];
 10     int pos[400], back, n;
 11     bool flag;
 12     void init(int b[], int size){
 13         n = size; flag = true;
 14         memcpy(order, b, sizeof(int)*n);
 15         memset(a, 0x3f, sizeof(LL)*n);
 16     }
 17     bool check(int j1, int j, int j2){
 18         return (a[j2] - a[j]) * (order[j] - order[j1]) <= (a[j] - a[j1]) * (order[j2] - order[j]);
 19     }
 20     LL get(int i){ return a[i] + tag * order[i] + delta; }
 21     void update(){
 22         for (int i = 0; i < n; i++)
 23             a[i] = get(i);
 24         tag = delta = 0; back = 0;
 25         flag = false;
 26         for (int i = 0; i < n; i++){
 27             while (back>1 && check(pos[back - 1], pos[back], i))back--;
 28             pos[++back] = i;
 29         }
 30         while (back > 1 && get(pos[back - 1]) <= get(pos[back]))back--;
 31     }
 32     void set(int i, LL val){
 33         a[i] += val - get(i);
 34         flag = true;
 35     }
 36     void add(int l, int r, int d){
 37         if (l == 0 && r == n - 1)delta += d;
 38         else{
 39             for (int i = l; i <= r; i++)
 40                 a[i] += d;
 41             flag = true;
 42         }
 43     }
 44     void add2(int l, int r){
 45         if (l == 0 && r == n - 1){
 46             tag++;
 47             while (back > 1 && get(pos[back - 1]) <= get(pos[back]))back--;
 48         }
 49         else{
 50             for (int i = l; i <= r; i++)
 51                 a[i] += order[i];
 52             flag = true;
 53         }
 54     }
 55     LL queryMin(int l, int r){
 56         if (l == 0 && r == n - 1){
 57             if (flag)update();
 58             return get(pos[back]);
 59         }
 60         LL ret = 1LL << 60;
 61         for (int i = l; i <= r; i++)
 62             ret = min(ret, get(i));
 63         return ret;
 64     }
 65 }b[1000];
 66 int a[100002], order[100002];
 67 int belong[100002], offset[100002], blockSize;
 68 void add(int l, int r, int delta){
 69     int start = l / blockSize, end = r / blockSize;
 70     for (int i = start; i <= end; i++)
 71         b[i].add(i == start ? offset[l] : 0, i == end ? offset[r] : b[i].n - 1, delta);
 72 }
 73 void add2(int l, int r){
 74     int start = l / blockSize, end = r / blockSize;
 75     for (int i = start; i <= end; i++)
 76         b[i].add2(i == start ? offset[l] : 0, i == end ? offset[r] : b[i].n - 1);
 77 }
 78 LL queryMin(int l, int r){
 79     int start = l / blockSize, end = r / blockSize;
 80     LL ret = 1LL << 60;
 81     for (int i = start; i <= end; i++)
 82         ret = min(ret, b[i].queryMin(i == start ? offset[l] : 0, i == end ? offset[r] : b[i].n - 1));
 83     return ret;
 84 }
 85 int main(){
 86     int n;
 87     scanf("%d", &n);
 88     for (int i = 1; i <= n; i++){
 89         scanf("%d", &a[i]);
 90         order[i] = a[i];
 91     }
 92     order[0] = 0;
 93     sort(order, order + n + 1);
 94     int cnt = unique(order, order + n + 1) - order;
 95     blockSize = sqrt(cnt);
 96     int j = 0, k = 0;
 97     for (int i = 0; i < cnt; i++){
 98         belong[i] = k;
 99         offset[i] = j++;
100         if (j == blockSize){
101             b[k].init(order + i - j + 1, j);
102             j = 0; k++;
103         }
104     }
105     if (j)b[k].init(order + cnt - j, j);
106     b[0].set(0, 0);
107     int mpos = 0;
108     for (int i = 1; i <= n; i++){
109         int pos = lower_bound(order, order + cnt, a[i]) - order;
110         if (pos >= mpos)mpos = pos;
111         else{
112             LL val = queryMin(0, pos);
113             b[belong[pos]].set(offset[pos], val);
114             add(0, pos - 1, order[mpos] - order[pos]);
115             add(pos + 1, mpos, -order[pos]);
116             add2(pos + 1, mpos);
117         }
118     }
119     printf("%lld", queryMin(0, cnt - 1));
120 }