1012. 增長率問題
Description
有一個數列,它是由自然數組成的,並且嚴格單調上升。最小的數不小於S,最大的不超過T。現在知道這個數列有一個性質:后一個數相對於前一個數的增長率總是百分比下的整數(如5相對於4的增長率是25%,25為整數;而9對7就不行了)。現在問:這個數列最長可以有多長?滿足最長要求的數列有多少個?
Input Format
輸入僅有一行,包含S和T兩個數( 0<S<T≤200000 )。
30%的數據,0<S<T≤100 ;
100%的數據,0<S<T≤200000。
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; }