第1部分 基礎算法(提高篇)--第3章 深搜的剪枝技巧-1615:【例 1】序列的第 k 個數

1615:【例 1】序列的第 k 個數

時間限制: 1000 ms 內存限制: 524288 KB
提交數: 2066 通過數: 619
【題目描述】
BSNY 在學等差數列和等比數列,當已知前三項時,就可以知道是等差數列還是等比數列。現在給你序列的前三項,這個序列要麼是等差序列,要麼是等比序列,你能求出第 k 項的值嗎。 如果第 k 項的值太大,對 200907 取模。

【輸入】
第一行一個整數 T,表示有 T 組測試數據;

對於每組測試數據,輸入前三項 a,b,c,然後輸入 k。

【輸出】
對於每組數據輸出第 k 項的值,對 200907 取模。

【輸入樣例】
2
1 2 3 5
1 2 4 5
【輸出樣例】
5
16
【提示】
樣例說明:

第一組是等差序列,第二組是等比數列。

數據範圍與提示:

對於全部數據,1≤T≤100,1≤a≤b≤c≤109,1≤k≤109 。


思路:
1.設所有木棍長度和爲maxn,那麼原長度(也就是需要輸出的長度)一定能夠被maxn整除,這樣得到的木棍根數纔是整數
2.木棍原來的長度一定不小於所有木棍中最長的那根
綜上兩點,可以確定原木棍的長度len在最長木棍的長度minx和maxn之間取值,且maxn能被len整除。所以在搜索原木棍的長度時,可以從砍過以後所有木棍中最長的長度開始,每次增加長度後,必須能整除maxn。

1.短木棍更加靈活,長木棍受到的限制更大,所以可以對輸入的所有木棍按長度從大到小排序。
2.在砍斷後的排好序的木棍中,當用木棍 i 拼合原始木棍時,可以從i+1的木棍開始往後搜,因爲i前面的木棍已經用過了
3.從當前最長的木棍開始搜,如果拼不出當前設定的原木棍長度len則直接返回,換一個原始木棍長度len
4.相同長度的木棍不要搜索多次.用當前長度的木棍搜下去得不出結果時,用一支同樣長度的還是得不到結果,所以可以提前返回
5.判斷搜到的幾根木棍組成的長度是否大於原始長度len,如果大於沒必要搜下去,可以提前返回
6.判斷當前剩下的木棍根數是否夠拼成木棍,如果不夠,肯定拼合不成功,直接返回
7.找到結果後,在能返回的地方馬上返回到上一層的遞歸處

#include<cstdio>
#include<cstring> 
#include<algorithm>
using namespace std;
int n,t,a[70],cnt,minx,maxn,len,nxt[70],m;
//a[i]是用來記錄每個標號(cnt)所代表的數 //minx和maxn分別表示下限和上限 //len表示的是我們的區間中的值 
//nxt[i]表示的就是記錄當前相同的數的版塊 //m表示我們預估的這個原來的根數
bool vis[70],flag1; 
bool cmp(int x,int y) 
{
    return x>y;
}
void dfs(int k,int last,int res)
{
    //k表示當前木棍編號,last爲正在拼的木棍的前一節編號,res爲還需長度
    if(res==0)//還需長度爲0,證明拼完了
    {
        if(k==m)//m根木棍都拼好了
        {
            flag1=true;//記錄爲成功,也就是找到了一個可以執行目標 
            return;//返回這個答案到上一個遞歸 
        }
        int i;
        for(i = 1;i <= cnt;i++)//又找到一個還沒用過的木棍
            if(vis[i]==false) break;//如果我們判斷這是沒用過的就退出這個循環,進入到下面 
        vis[i] = true; 
        dfs(k+1,i,len-a[i]);//用它拼接
        vis[i] = false;
        if(flag1 == 1) return;//找到答案就可以層層退出
    }
    int l = last+1,r = cnt,mid;
    while(l < r)//二分查找下一次拼接的木棍,該木棍是不大於所需長度的第一根木棍
    {
        mid=(l+r)>>1; 
        if(a[mid] <= res) r=mid;//如果小於我們所需要的就要往左邊找,因爲我們的a數組是排序過從大到小 
        else l = mid+1;//否則往右邊找,排序過的從大到小 
    }
    for(int i = l;i <= cnt;i++)/*從l開始縮短搜索範圍,之間在這個還需要的長度的區間當中找合適的,
    由於所有木棍是按從大到小的順序排的,因此從上述找到的木棍向右枚舉*/
    {
        if(vis[i] == false)
        {
            vis[i] = true;
            dfs(k,i,res-a[i]);
            vis[i]=false;
            if(flag1 == true) return;//如果是我們找到目標就返回答案 
            if(res==a[i] || res==len) return;
            /*當前正在拼的長棍剩餘的未拼長度等於當前小木棍的長度,
            說明它只能自組,但繼續拼下去卻失敗,說明它不能自組。*/
            
            /*當前木棍剩餘的未拼長度等於原始長度,說明這根原來的長棍還一點沒拼。
            還需要繼續拼接,但繼續拼下去卻失敗 ,所以無法用上它。*/
            //flag1=0 表示 繼續拼下去失敗
            i = nxt[i];//把我們找到的這個i的值記錄到nxt數組的模塊當中 
            if(i == cnt) return;/*如果兩個是相同的話,
            就是說我們已經到了這個循環的最後一個*/ 
        }
    }
    return; 
}
int main()
{
    scanf("%d",&n); 
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&t);
        if(t <= 50)/*限制條件:每段的長不能超過這個50*/
        {
            a[++cnt] = t;
            minx = max(minx,a[cnt]);//下界
            maxn += a[cnt];//上界
        }
    }
    sort(a+1,a+cnt+1,cmp);
    nxt[cnt]=cnt;/*記錄當前有多少個相同的數的版塊只,能記錄就是nxt[cnt]=cnt
    所以剛開始只有最後一個是成立的*/ 
    for(int i=cnt-1;i>0;i--)//cnt是我們擁有的個數,所以我們的這個版塊的排序是從小到大,而不是從大到小 
    {
        if(a[i] == a[i+1]) nxt[i]=nxt[i+1];/*如果前後兩個數是一樣的話,
        那麼他們所代表的大小順序也是一樣的,就把他們分成同一版塊*/ 
        else nxt[i]=i;//否則就各自一個佔用一個模塊 
    }
    for(len = minx;len <= maxn/2;len++)/*長度最小就是我們定義的下線,
    最大也不可能超過總和的一半,因爲至少要分成兩組*/
    {
        if(maxn % len == 0)//合法
        {
            m = maxn/len;
            flag1 = 0;
            vis[1] = 1;/*初始化全部都可以用*/
            dfs(1,0,len-a[1]);/*從第一根木棍開始*/
            vis[1] = 0;//恢復現場
            if(flag1 == 1)//找到了目標(最上面的那一層遞歸返回來的) 
            {
                printf("%d\n",len);//就輸出當前我們找到的這麼len的值 
                return 0; 
            }
        }
    }
    printf("%d\n",maxn);/*如果找不到這個區間當中的len的話,
    就直接輸出全部長度,也就是隻有一個木棍*/ 
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章