POJ2109 Power of Cryptography (高精度+二分) (可以double水過去)

Description

Current work in cryptography involves (among other things) large prime numbers and computing powers of numbers among these primes. Work in this area has resulted in the practical use of results from number theory and other branches of mathematics once considered to be only of theoretical interest.
This problem involves the efficient computation of integer roots of numbers.
Given an integer n>=1 and an integer p>= 1 you have to write a program that determines the n th positive root of p. In this problem, given such integers n and p, p will always be of the form k to the n th. power, for an integer k (this integer is what your program must find).

Input

The input consists of a sequence of integer pairs n and p with each integer on a line by itself. For all such pairs 1<=n<= 200, 1<=p<10 101 and there exists an integer k, 1<=k<=10 9 such that k n = p.

Output

For each integer pair n and p the value k should be printed, i.e., the number k such that k n =p.

Sample Input

2 16
3 27
7 4357186184021382204544

Sample Output

4
3
1234

Source

México and Central America 2004


剛拿到這道題,想到的就是高精度加上二分。敲完後TLEandWA了好幾次,一直改了兩三個小時,還是ac不了。無奈之下,看了一下discuss。頓時無奈了。數據是有問題的,有可能存在無解的情況,所以使用高精度+二分去做的話,還要特殊處理一下,如果存在無解,則輸出偏小的那個值。最後終於ac了。

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;

const int maxn = 21;
const int mod = 1e8;
int n;
string p;
long long now[maxn], form[maxn]; //1e8 for each

void run(int fac)
{
    for(int i = 0; i < 20; ++i) now[i] *= (long long)fac;
    for(int i = 0; i < 20; ++i) {
        now[i+1] += now[i] / mod;
        now[i] = now[i] % mod;
    }
}

int judge(int x)
{
    memset(now, 0, sizeof(now));
    now[0] = x;
    for(int i = 1; i < n; ++i) {
        run(x);
        for(int j = maxn - 1; j >= 0; --j) {
            if(now[j] > form[j]) return 1;
            else if(now[j] < form[j]) break;
        }
    }
    for(int i = maxn-1; i >= 0; --i) {
        if(now[i] > form[i]) return 1;
        else if(now[i] < form[i]) return -1;
    }
    return 0;
}

int main()
{
//    freopen("in", "r", stdin);
    while(cin >> n >> p) {
        memset(form, 0, sizeof(form));
        int cur = 0, bit = 1;
        for(int i = p.length()-1; i >= 0; --i){
            if(bit >= mod) {bit = 1; cur++;}
            form[cur] += bit * (p[i]-'0');
            bit *= 10;
        }
        int l = 1, r = 1e9;
        int ok = 0;
        while(l <= r) {
            int m = (r+l) / 2;
            int c = judge(m);
//            printf("m = %d c=%d l=%d r=%d\n", m, c, l, r);
            if(c == 0) {printf("%d\n", m); ok = 1; break;}
            else if(c > 0) r = m - 1;
            else if(c < 0) l = m + 1;
        }
        if(!ok) printf("%d\n", l-1);
    }
    return 0;
}

最後附上另一個博主的文章。

转载请注明出处:優YoU  http://user.qzone.qq.com/289065406/blog/1299228474

 

提示:

一般思路:二分+高精度算法

 

但是本题还有一个更加巧妙的办法去处理:

首先需要明确:double类型虽然能表示10^(-307)   ~   10^308, (远大于题意的1<=p<10101这个范围),但只能精确前16位,因此必须慎用!

那么为了避免double对输入的数在运算过程中进行精确,那么我们必须让double的运算第一步就得到一个int(即小数点尾数全为0),这个不难理解。

 

然后根据题意,是求指数k,一般人自然想到利用 对数log,即k=lognp。但是不要忘记使用对数最大的问题就是没有lognp函数,只有log()函数(底数为e),为此要计算lognp就必须使用换底公式lognp=log(p)/log(n),即k= log(p)/log(n),由于这使得double的运算变为了3次,而且执行除法前的两次对数运算log的结果未必都是int,很显然k是一个被精确了的double

 

很多人到这里就放弃了使用double,转换方向到正常思路(二分+高精度算法),但是不要忘记求指数k除了使用对数log,还能使用指数的倒数开n次方,这时就可以用pow函数了

k=pow(p,1.0/n),double的运算一步到位,k自然也是一个int

//Memory Time 
//280K    0MS 

#include<iostream>
#include<math.h>
using namespace std;


int main(void)
{
	double n,p;
	while(cin>>n>>p)
		cout<<pow(p,1.0/n)<<endl;  //指数的倒数就是开n次方
	return 0;
}

 


哈哈,不要惊讶!程序就是这么短,这就是“技巧”与“算法”的差别

用double避开高精度算法,可以说是这题最大的BUG  O(∩_∩)O


還有另外一種二分,不需要使用高精度

#include<stdio.h>
#include<math.h>
#define eps 0.0000000001
void init(), work();
double n, m, k;
int main()
{
    init();
    return 0;
}
void init()
{
    while(scanf("%lf %lf", &n, &m) != EOF)
        work();
}
void work()
{
    long long left, right, mid;
    left = 0;
    right = 1000000002;
    while(left + eps < right){
        mid = (left + right) / 2;
        if(pow(mid, n) - m > 0)
            right = mid;
        else
            if(pow(mid, n) - m < 0)
                left = mid;
            else{
                printf("%.0ld\n", mid) ;
                break;
            }
    }
}

类型          长度 (bit)           有效数字          绝对值范围
float             32                      6~7                  10^(-37) ~ 10^38
double          64                     15~16               10^(-307) ~10^308
long double   128                   18~19                10^(-4931) ~ 10 ^ 4932


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值