湖南省第八届大学生程序设计大赛原题 D

D - 平方根大搜索(UVA 12505 )
Time Limit:5000MS     Memory Limit:131072KB     64bit IO Format:%lld & %llu

Description

在二进制中,2的算术平方根,即sqrt(2),是一个无限小数1.0110101000001001111...
给定一个整数n和一个01串S,你的任务是在sqrt(n)的小数部分(即小数点之后的部分)中找到S第一次出现的位置。如果sqrt(n)是整数,小数部分看作是无限多个0组成的序列。

Input

输入第一行为数据组数T (T<=20)。以下每行为一组数据,仅包含一个整数n (2<=n<=1,000,000)和一个长度不超过20的非空01串S。

Output

对于每组数据,输出S的第一次出现中,第一个字符的位置。小数点后的第一个数字的位置为0。输入保证答案不超过100。

Sample Input

2
2 101
1202 110011 

Sample Output

2
58 

题意:将一个数开平方,得到小数部分和给定的字符串比较,输出相应的第一个位置
分析:double存不下100位的精度,只有用模拟。题目太难,看解题报告理解了好久,下面是标程,加上了自己理解的注释,可能写的比较乱,表达不是很好,还望见谅

#include<cstdio>
#include<cstring>
const int maxn=1111;
char s[maxn];
int a[maxn],b[maxn],res[maxn];
int main(){
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d%s",&n,s);
        memset(a,0,sizeof a);
        //将n分解成二进制,保存到a[280]->a[299];
        for(int i=19;i>=0;i--){
            if(n>=(1<<i)){
                n-=(1<<i);
                a[i+280]=1;
            }
        }
        memcpy(res,a,sizeof(a));//为什么要复制?多此一举的开了一个a数组
        for(int i=149;i>=0;i--){
            int b1=0;//标记
            /*为什么这里是149,而后面是139?因为前十位存的是整数部分!n化成二进制最多要20位,开埂号后最后为10位*/
            for(int j=299;j>149+i+1;j--)//目前查找到的开方数有149-i位,299-(149-i)=150+i;
                if(res[j]==1){
                    /*前面如果某一位为1;当前b[i]必定上1,因为在后面加上一个1,平方后仍然小于原来的数;*/
                    b1=1;break;
                }

            if(b1==0)//如果为0,比较res和b;确保商1的话,得到的平方小于n’;
                for(int j=149;j>i;j--){
                    if(res[j+i+1]>b[j]){
                        b1=1;//一位一位比较,先找到比他大的,说明可以商1,先找到的比它小,那么只能商0
                        break;
                    }
                    if(res[j+i+1]<b[j]){
                        b1=-1;break;
                    }
                }
            //我们这里来看看,00*00=0000,01*01=0001,10*10=0100,11*11=1001;
            //11*11=1001,111*111= 110001,110*110=100100,1111*1111= 11100001,1110*1110=11000100
            //商零,对前面的几位并没有任何影响,最后两位为0,不用修正,
            //商1,最后两位为01,对前面的数有一定的影响,需要修正
            if(b1==-1||b1==0&&res[i+i]==0&&res[i+i+1]==0) b[i]=0;
            /*与b[i]对应的连续的两位都为0的话,b[i]只能上0,上1的话res[i*i]必定为1;*/
            else{
                b[i]=1;//商1,res减去对应位的b,
                for(int j=149;j>i;j--)
                    res[j+i+1]-=b[j];//前面的减b[j],将n逐渐逼向0
                res[i+i]--;//最后一位减1,倒数第二位为0,不需要修正
                //修正res的值,将小于0的位补正
                for(int j=i+1;j<300;j++)
                    if(res[j]<0){
                        /*如果某位小于零,则需要借位,将这一位加2(因为是二进制),下一位减一*/
                        res[j]+=2;
                        res[j+1]--;
                    }
                //修正后的res一定大于0,否则不会商1.如果下次商0,得到的平方不会改变,不用修改res,
                //如果商1,得到的平方必定小于n,将n剪掉b,不停的用二分,将n逼近于0
            }
        }
        n=strlen(s);
        //b中保存的是平方根(倒着保存的,即最后一位是小数点后第一位)
        for(int i=139;i>=0;i--){
            int b1=1;
            for(int j=0;j<n;j++)//对比,完全一样的就输出起始位置
                if(b[i-j]!=s[j]-'0') b1=0;
            if(b1){
                printf("%d
",139-i);
                break;
            }
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/wabi87547568/p/4689924.html