1. 程式人生 > >【2018 1月集訓 Day1】二分的代價

【2018 1月集訓 Day1】二分的代價

題意:

  現在有一個長度為 n的升序陣列 arr 和一個數 x,你需要在 arr 中插入 x

  你可以詢問 x 跟 arri 的大小關係,保證所有 arri x 互不相同。這次詢問的代價為 costi

  你需要返回 x 應該插入的位置,顯然有 n+1 中可能的返回值。

  現在給你 cost 陣列,你需要制定方案,使得對於所有可能的情況花費代價(即詢問的代價的和)的最大值最小,輸出這個最小值。

  制定方案的意思就是說你先詢問一個 i,然後根據返回值決定接下來詢問哪個 i,直到你可以確定答案為止。

 

分析:

  這個題好神啊……我看了ztb大爺的程式碼……可能我理解的也不是很準確啊……那就三個月後再戰此題吧……

  首先可以看到,每個ai不超過9,所以最終的答案一定不大,最變態的上界也不過就是9logn,但是應該達不到。

  我們設兩個dp陣列:

    f[i][v]表示以i為當前區間的左端點,花費為v,最長能確定的區間的右端點。

    g[i][v]表示以i為當前區間的右端點,花費為v,最長能確定的區間的左端點。(其實就是對稱的)

  那麼我們可以看到,對於一個costi,假如我們付出這樣的代價,那麼在暫時不考慮左端點的情況下,最長的區間的右端點一定在f[i+1][v-costi],那我們該用這個點去更新哪個狀態呢???

  即為這個可行區間找一個最左端點,那麼我們另一個數組就派上用場了,花費已經確定,那麼我們就令:

  f[g[i][v-costi]][v]=max(f[g[i][v-costi]][v], f[i+1][v-costi]);

  或者換一種表達方式,就是在g[i][v-costi]表示的這個點,我們花costi的花費,使總花費達到v,我們所能達到的最右端點可能是在i+1這個點花費v-costi能達到的最右端點。

  另一個方程同理:g[f[i+1][v-costi]][v]=min(g[f[i+1][v-costi]][v],g[i][v-costi]);

  當然,初始化就是f[i][v]=g[i][v]=i;

  但是,有些costi由於太過不優,我們決策時會直接跳過,但是它會被之前某些決策所覆蓋,所以也是需要更新的,於是就多了兩個for迴圈來保證所有答案合法且最優。

  當f[1][v]覆蓋整個區間時,v就是題目的答案。

程式碼:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=100005,M=140;
 4 int a[N],f[N][M],g[N][M],n;char s[N];
 5 int main(){
 6     scanf("%s",s+1);n=strlen(s+1);
 7     for(int i=1;i<=n;i++) a[i]=s[i]-'0';
 8     for(int v=0;v<M;v++)
 9     for(int i=1;i<=n+1;i++)
10     f[i][v]=g[i][v]=i;
11     for(int v=1;v<M;v++){
12         for(int i=1;i<=n;i++){
13             if(v<a[i]) continue;
14             f[g[i][v-a[i]]][v]=max(
15             f[g[i][v-a[i]]][v],f[i+1][v-a[i]]);
16             g[f[i+1][v-a[i]]][v]=min(
17             g[f[i+1][v-a[i]]][v],g[i][v-a[i]]);
18         } for(int i=2;i<=n+1;i++)
19         f[i][v]=max(f[i][v],f[i-1][v]);
20         for(int i=n;i;i--)
21         g[i][v]=min(g[i][v],g[i+1][v]);
22         if(f[1][v]==n+1) 
23         {printf("%d\n",v);return 0;}
24     } return 0;
25 }
dp