【BZOJ1236】KPSUM,记数类问题(乱搞)

传送门
权限题,放一下题面

1236: SPOJ1433 KPSUM

Time Limit: 1 Sec Memory Limit: 162 MB
Submit: 163 Solved: 87
[Submit][Status][Discuss]
Description

给你一个正整数N,依次把1到N写下来,在每两个数字之间交替的加上+,-号(注意是数字不是数),求这个表达式的值。例如,N=15时,表达式为1-2+3-4+5-6+7-8+9-1+0-1+1-1+2-1+3-1+4-1+5

Input

一个正整数N。

Output

表达式的值。

Sample Input

15

Sample Output

14

数据范围:30%的数据中,N<=100。

100%的数据中,N<=10^15

写在前面:听Shallwe说,这是一篇论文的练习题= =
思路:(调了好久才A,为zky学长的号+AC尽一份力)
第一反应是打表找规律,但是发现这规律并不是那么直接明显,我们进一步分析发现,计算奇数位的数时,相邻的两个数和为1(-2+3,-1+0-0+1-0+1),所以可以直接算出来(即5,450,45000…)。为了方便,我们称形同(2,3)(100,101)的两个数为“数对”。计算偶数位时,很显然每一位前面的运算符是固定的,都是-..+..-..+..,个位、百位……前都是+,十位、千位……前都是-,所以我们可以类似递推得预处理一下,f[i][j]指第i位最大为j时的总和(直接点说,就是0-j999……9,j是第i位),但是都是偶数位的情况下,可能有点难理解,
举个例子说f[1][9]=0+1+2+3+4+5+6+7+8+9
f[2][1]=-1+0-1+1-1+2-1+3-1+4-1+5-1+6-1+7-1+8-1+9,
显然转移时就是
f[i][j]=f[i1][9](j=0)
f[i][j]=f[i][j1]+f[i1][9]+j10i1(i)
f[i][j]=f[i][j1]+f[i1][9]j10i1(i)
转移出来后就比较好处理了,这里我们分别以120和2456作为讨论

对120来说,我们可以分块计算0-9和10-99,0-9是奇数位数,直接ans+=5(显然,奇数位时答案直接加5,450,45000……),10-99为偶数位数,我们要ans+=f[2][9],但是f[2][9]里包含了f[2][0],即十位数最高为2的情况包含了十位为0的情况,但是我们的计算是从10开始的,所以要减去f[2][0]
对于100-120,它是奇数位数,所以利用之前得到的性质,计算100-120之间有多少个“数对”,显然这一段对答案的贡献为(120-1-100)/2-1+2-0(最后的120是孤立的,单独计算即可)

对2456来说,形同上计算0-9,10-99,100-999,最后计算1000-2456,这是偶数位数,我们可以继续把它分解成1000-1999(即f[4][1]-f[4][0]),2000-2399(即f[3][3]-2*400),2400-2449(即f[2][4]-2*50+4*50)最后计算时要在个位特判一下,普遍规律就是可以记录一下前边的数数字按位计算的和差×10的几次方×当前位数字(这里有些口胡,因为不是很(想+会)用式子表达)
注意:
还是补充一下个位特判好了,例如计算2400时,在十位的0是不计算的,但个位的0也是一次计算,所以要计算
代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL x,k,n,ans; 
LL p[20]={1},s[20]={0,5,0,450},f[20][10],wei[20];
LL fj(LL x)
{
    LL ans=0;
    for (int i=1;i<=20;i++)
    {
        if (!x) return ans;
        if (i&1) ans+=x%10;
        else ans-=x%10;
        x/=10;
    }
}
main()
{
    scanf("%lld",&n);
    for (int i=1;i<=17;i++) p[i]=p[i-1]*10;//p[i]=10^i
    for (int i=5;i<=17;i+=2) s[i]=s[i-2]*100;//5,450,45000……
    for (int i=1;i<=17;i++)
    for (int j=0;j<=9;j++)
    {
        if (!j) f[i][j]=f[i-1][9];
        else if (i&1) f[i][j]=f[i][j-1]+f[i-1][9]+p[i-1]*j;
        else f[i][j]=f[i][j-1]+f[i-1][9]-p[i-1]*j; 
    }
    for (int i=1;i<=16;i++)
    {
        if (p[i]>n) {k=i-1;break;}
        if (i&1) ans=ans+s[i];
        else ans=ans+f[i][9]-f[i][0];
    }
    if (k&1)
    {
        LL q=0,now=n;
        for (int i=1;i<=k+1;i++)
        wei[i]=now%10,now/=10;
        ans-=f[k+1][0];
        for (int i=k+1;i>=1;i--)
        {
            if (wei[i]||i==1) ans=ans+q*(wei[i]+(i==1))*p[i-1]+f[i][wei[i]+(i==1)-1];
            if (i&1)q+=wei[i];
            else q-=wei[i];
        }
    }
    else
    {
        if (n&1)
        ans+=(n-p[k])/2+1;
        else ans=ans+(n-p[k]+1)/2-fj(n);
    }
    printf("%lld",ans);
}

你可能感兴趣的:(【BZOJ1236】KPSUM,记数类问题(乱搞))