第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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章