URAL 1749 Periodic Sum (二分法 + 溢出处理)

题目大意:

给出 p , p是一个很大的数,其位数在10^5位以内,定义S( t )为一个整数中去任意的连续 i 位得到的数的和, i 从1到 t 的长度

比如 t = 1025时, S(1025) = 1 + 0 + 2 + 5 + 10 + 02 + 25 + 102 + 025 + 1025 = 1575

定义F( t , k ) 为将 t 重复写 k 次得到的新的数, 比如 F( 1025 , 3 ) = 102510251025

现在要求求出 S( F( p , k ) )

输入给出 p, k ,m 要求输出 S( F( p , k ) ) mod m;


大致思路:

首先这道题求出 S ( F( p , k ) ) 的通项是不方便取模的,这里写出我的计算过程:

先考虑没有 k 次重复的情况, 对于一个数 p ,先来求 S( p )

将数 p 用字符串储存之后得到每一位的数字

设s[1] ~ s[len] 依次是数 p 的最低位到最高位, len 是字符串 p  的长度

那么 对于从低到高的第 i 位的数 s[ i ] 在和 S( p ) 中出现的情况如下:

s[1]作为个位出现 len 次

s[2] 作为个位出现 len - 1 次, 作为十位出现 len - 1次

s[3] 作为个位出现 len - 2次, 作为十位出现 len - 2 次, 作为百位出现 len - 3次

.....

这个很容易证明,这里不给出证明,自己画一下就明白了

 s[ i ] 作为个位出现 len - i + 1次,作为十位出现 len - i + 1次....作为数级是 10^(i - 1)  的位出现 len - i + 1次

那么对于数 p , 设其长度为 len , 从最低位到最高位的数依次是 s[1] ~ s[len]

那么:

那么这里到 p 重复 k 次时的情况便是 len 变为k*len而 p 这个数变成重复k次得到的数

那么便有 s[ i + c*len ]  = s[ i ], c 从0到 k - 1


然后由于本题中数据范围很大,最后的结果是取模 m 输出

那么由于分母有一个9, 取的模并不保证是一个质数,也就不能使用费马小定理转换


注意到分子一定能被9整除,可以发现这样一个事实:

对于整数A有  A mod B = ((9*A) mod (9*B)) / 9;

这是一个很容易证明的结论,证明如下:

设 A = a*B +b;

那么 A mod B = b 

9*A mod(9*B) = (9*a*B +9*b) mod (9*B) = 9*b

所以 A mod B = ((9*A) mod (9*B)) / 9;


也就是说 对于S(p) % m, 我们可以求 (S(p) * 9) % (9*m) 将最后的结果除以9即可

那么我们现在可以不考虑分母9的问题,但是这样做会造成一个隐患,由于现在取模m 变成了取模 9*m, 对于两个小于 9*m的数相乘范围在 1.8*10^19内,超出了long long 的范围, 这样便不能直接将两个long long型在9*m范围内的数直接相乘取模,这里用到一个方法,代码和快速幂相似,成功避免了两个数直接乘超出数据范围的情况,主要思想是将一个long long拆成多个数来一次相乘将得到的结果相加取模,避免溢出,具体见代码中的 mul 函数部分


那么继续将这个公式推至S( F( p, k) ) 的形式:


现在来看最后的这个式子,变量 i 从1到 len 可以直接循环 (len <= 100000)

剩下的就是两个求和式的问题,对于sigma(base^i)  i 从0 到 k-1 可以每次二分来求

用Geo(base, pow) 表示 sigma(base^i) i 从0 到pow - 1 有

Geo(base, 0) = 1;

Geo(base, pow) = Geo(base, pow - 1) + base^(pow - 1) (pow 是奇数时)

Geo(base, pow) = Geo(base, pow / 2) * ( 1 + base^(pow / 2)) (pow 是偶数时)

这个做法在之前写的 Codeforces 327C Magic Five那道题求等比数列的和的时候也用到过

那么对于sigma( i * base^i ) i 从0 到 pow - 1来说也可以用二分来求

用AriGeo(base, pow) 表示sigma( i * base^i ) i 从0 到pow - 1有:

AriGeo(base, 0) = 0;

AriGeo(base, pow) = AriGeo(base, pow - 1) + (pow - 1)*base^(pow - 1) (pow 是奇数时)

AriGeo(base, pow) = AriGeo(base, pow ./ 2)(1 + base^(pow./ 2)) + (pow / 2) * (base^(pow / 2)) * Geo(base, pow / 2)  (pow 是偶数时)

上面这个式子可以自己验证一下

对于每一个求幂的式子,都用快速幂的方法来求

这样整个复杂度便降下来了,注意在减法操作之后加上要取模的数之后取模,防止负数出现


整个思路和注意的要点都已经说完了,接下来是代码:

Result  :  Accepted     Memory  :  2021 KB     Time  :  875 ms

/*
 * Author: Gatevin
 * Created Time:  2014/7/27 11:10:56
 * File Name: test.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

char ss[100010];
lint s[100010];
lint k,m,mod,len;
lint ten[100010];

lint mul(lint a, lint b)
{
    lint ret = 0;
    while(b)
    {
        if(b & 1)
        {
            ret = (ret + a) % mod;
        }
        b >>= 1;
        a= (a*2) % mod;
    }
    return ret;
}

lint quick_pow(lint base, lint pow)
{
    lint ret = 1;
    while(pow)
    {
        if(pow & 1)
        {
            ret = mul(ret, base);
        }
        pow >>= 1;
        base = mul(base, base);
    }
    return ret;
}

//Evaluate the sum of geometric sequence sigma(base^i) , i range from 0 to pow - 1
lint GeoSeq(lint base, lint pow)
{
    if(pow == 1) return 1;
    if(pow & 1) return (GeoSeq(base, pow - 1) + quick_pow(base, pow - 1)) % mod;
    else return mul(GeoSeq(base, pow >> 1), (1 + quick_pow(base, pow >> 1)) % mod);
}

/* 
 * Evaluate the sum of the sequence which is a combination of 
 * an arithmetic sequence and a geomatric sequence sigma(i*base^i), i range from 0 to pow - 1
 */
lint AriGeoSeq(lint base, lint pow) 
{
    if(pow == 1) return 0;
    if(pow & 1) return (AriGeoSeq(base, pow - 1) + mul(pow - 1, quick_pow(base, pow - 1))) % mod;
    else return (mul(AriGeoSeq(base, pow >> 1), 1 + quick_pow(base, pow >> 1))
                + mul(mul(pow >> 1, quick_pow(base, pow >> 1)), GeoSeq(base, pow >> 1))) % mod;
}

lint solve()
{
    lint answer = 0;
    ten[0] = 1;
    for(int i = 1; i <= len; i++)
    {
        ten[i] = (ten[i - 1] * 10) % mod;
    }
    lint tmp1 = GeoSeq(ten[len], k);
    lint tmp2 = AriGeoSeq(ten[len], k);
    lint tmp3 = (k*(k - 1)/2) % mod;
    for(int i = 1; i <= len; i++)
    {
        answer = (answer + mul(s[i], (mul(mul(tmp1, (k*len - i + 1) % mod), ten[i]) - mul((k*len - i + 1) % mod, k)
                        - mul(mul(tmp2, len), ten[i]) + mul(len, tmp3) + 2*mod) % mod)) % mod;
    }
    return answer;
}

int main()
{
    scanf("%s",ss);
    scanf("%I64d%I64d",&k,&m);
    mod = 9*m;
    len = strlen(ss);
    for(int i = 0; i <= len - 1; i++)
    {
        s[len - i] = ss[i] - '0';
    }
    lint answer = solve();
    cout<<answer/9<<endl;
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值