【算法學習筆記】43.動態規划 逆向思維 SJTU OJ 1012 增長率問題


Description

有一個數列,它是由自然數組成的,並且嚴格單調上升。最小的數不小於S,最大的不超過T。現在知道這個數列有一個性質:后一個數相對於前一個數的增長率總是百分比下的整數(如5相對於4的增長率是25%,25為整數;而9對7就不行了)。現在問:這個數列最長可以有多長?滿足最長要求的數列有多少個?

Input Format

輸入僅有一行,包含S和T兩個數( 0<S<T200000 )。

30%的數據,0<S<T100 ;

100%的數據,0<S<T200000

Output Format

輸出有2行。第一行包含一個數表示長度,第二行包含一個數表示個數。

Sample Input

2 10

Sample Output

5
2

樣例解釋

2 4 5 6 9以及2 4 5 8 10

 

看着第一感覺就肯定是要用DP的,所以思考從S到T的問題,如何變為S到T-1的問題呢?

首先我們需要知道S到T-1的最長序列有幾個,它們的末端分別是誰,然后把T和他們的末端進行比較,看是否可以連上,從而決定是否增加長度,是否更新最長長度的個數。

因此,我們DP的狀態就是 d[i]表示以i為結尾的最長序列的長度 和 times[i]以i結尾的最長序列的個數 還有 cnt[x]長度為x的序列的個數

算法過程:

從S到T遍歷每個起點i

接下來有兩條路可以走: 正向思考 我們可以遍歷從i+1到T的每一個數去和i進行比較,判斷是否滿足條件,但是這樣肯定會超時,因為數據量太大。

另外一個想法就是逆向思維,我們最終的判斷是需要找到增長率的百分比為整數的那個數。

假設百分下整數為j,則確定的那個數 tmp 與 i的關系為

(tmp-i) / i = j / 100 整理可以得到 tmp = i + i*j/100 (可以看出 tmp為整數的條件要求 i*j%100==0)

得到了這個tmp(必須小於等於T)之后,開始進行DP狀態轉移

 

  狀態轉移關系如下: 

  首先,我們的tmp是在以i為結尾序列之后添加的,長度為d[i]+1, 以tmp的結尾的最長的序列長度和次數可能要更新

  如果d[tmp]和d[i]+1相等, 說明在此之前 以tmp結尾的序列的最長長度 和 剛剛得到的序列長度一致

    所以要將 times[tmp] += times[i] //在這個序列的d[i]部分的重復度為times[i] ( >=1 )

  如果d[tmp]<d[i]+1,說明新的序列長度比原來的還要長,所以不僅要更新最長長度d[tmp] 還要更新 最長長度的重復次數times[tmp]

    d[tmp] = d[i] + 1

    times[tmp] = times[i]

  之后,我們要更新總的最大長度ans :  ans = max(ans,d[tmp])

            更新當前序列所占長度的序列數 cnt[d[i]+1] += tims[i] * 1; (*1是為了更好的說明 times[i]的是d[i]部分的重復度 1表示tmp的重復度 , 和1285里的一個部分很像)

至此狀態轉移結束

最后只需輸出 ans 和 cnt[ans]即可

PS:說明一點,就是為什么j從1到100即可。 增長率從1%到100%的覆蓋區間恰好是,i到2i這個部分。

那么為什么是2i不是3i呢。可以舉個例子,比如序列x,x,x,x,x,i,x,x,x,3i的長度一定沒有x,x,x,x,x,i,x,,2i,x,x,3i 的長度長,所以考慮最優解只要在i到2i里找下一位即可。

當然,更嚴謹的數學證明會比較麻煩,需要證明在2i之后的每一個能夠滿足條件的tmp都可以從i,tmp 可以轉換至少為 i,x,tmp的結構 其中x小於2i。 有空再證明吧,反正AC了。。

代碼如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;

const int maxn=200007;
int d[maxn];
ll cnt[maxn],times[maxn];
//cnt[i]存儲的是 長度為i的序列的個數
//d[i]存儲的是以i結尾的序列最長的長度
int main()
{
    int s,t;
    cin>>s>>t;
    memset(cnt,0,sizeof(cnt));
    int i,j,tmp,ans=1;
    cnt[1]=t-s+1;

    for(i=s;i<=t;i++)
        d[i]=times[i]=1; //初始化為1

    for(i=s;i<=t;i++) //遍歷每個數作為起點
        for(j=1;j<=100;j++)    if( (i*j)%100 == 0) //假設增長率為j%
        {
            //則在此增長率下 得到的數為 tmp 可以看出,要讓tmp為整數 必須i*j是100的倍數
            tmp = i + i*j/100;
            if( tmp <= t ) //如果這個tmp是在規定范圍內的 我們就找到了一個解
            {
                if(d[i]+1 > d[tmp]) //這時要比較 看看要不要更新以tmp結尾的最長的長度
                {
                    d[tmp] = d[i] + 1;//以tmp為結尾的序列的長度 為 以i為結尾的序列的長度 再加上1 表示算上tmp
                    times[tmp] = times[i]; //發生了最長長度的更新 所以要重置 以tmp結尾的最長長度的重復次數為 i的
                }
                else if(d[i]+1==d[tmp]) //恰好是重復的最長長度 
                {
                    times[tmp] += times[i];//則最長長度的次數 增加 以i結尾的最長長度重復次數
                }

                ans = max(ans,d[tmp]);  //更新ans
                //d[i]+1 表示以i結尾的序列長度+tmp所增加的1位 這個長度的子列數量要增加times[i]
                cnt[d[i]+1] += times[i]; 
            }
        } 
    cout<<ans<<cnt[ans];    
    return 0;
}

 

 

 

 

 


免責聲明!

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



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