SPOJ KPSUM 数位DP

题意:
把1到n(1<=n<=1e15)写在一行,给每个数位间隔地加加减号,像这样:
n=12: +1 -2 +3 -4 +5 -6 +7 -8 +9 -1+0 -1+1 -1+2=5
给你n问上面这个式子的结果是多少

大致思路:
数位DP,找下规律发现数位长度为偶数的数都是以-开头,数位长度为奇数再分两种情况:这个数是偶数则以-开头,这个数是奇数则以+开头

那么开个记忆化搜索:
dfs(长度,当前符号,所求数位长度奇偶性,所求数奇偶性,前缀0标记,上限标记)

由于要计算权值所以还要再开一个记忆化搜索计算合法数的个数:
dfs2(长度,所求数奇偶性,上限标记)由于实现细节的关系这里省了数位长度奇偶性和前缀0标记

详细转移看代码,有比较多细节要注意
想了一晚上,不容易啊

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<iomanip>
#include<vector>
#include<set>
#include<map>
#include<queue>

using namespace std;
typedef long long LL;
typedef unsigned long long ULL;

#define rep(i,k,n) for(int i=(k);i<=(n);i++)
#define rep0(i,n) for(int i=0;i<(n);i++)
#define red(i,k,n) for(int i=(k);i>=(n);i--)
#define sqr(x) ((x)*(x))
#define clr(x,y) memset((x),(y),sizeof(x))
#define pb push_back
#define mod 1000000007
int a[20];
LL dp[20][3][2],dp2[20][2];

LL dfs2(int pos,int valp,bool up)
{
    if(!up && dp2[pos][valp]!=-1)return dp2[pos][valp];
    int upp=up?a[pos]:9;
    LL ret=0;

    if(pos==1)
    {
        rep(i,0,upp)if(i%2==valp)ret++;
    }
    else
    {
        rep(i,0,upp)ret+=dfs2(pos-1,valp,up&&i==a[pos]);
    }
    if(!up)dp2[pos][valp]=ret;
    return ret;
}

LL dfs(int pos,int sign,int lenp,int valp,bool z,bool up)
{
    if(!z && !up && pos%2==lenp && dp[pos][sign+1][valp]!=-1)return dp[pos][sign+1][valp];
    int upp=up?a[pos]:9;
    LL ret=0;

    if(pos==1)
    {
        if(z && pos%2!=lenp)return 0;
        rep(i,0,upp)if(i%2==valp)
        {
            ret+=sign*i;
        }

    }
    else
    {
        if(z)
        {
            ret+=dfs(pos-1,sign,lenp,valp,1,up && a[pos]==0);
            if(pos%2==lenp)
            {
                rep(i,1,upp)
                    ret+=dfs(pos-1,-sign,lenp,valp,0,up && i==a[pos])+i*sign*dfs2(pos-1,valp,up && i==a[pos]);
            }
        }
        else
        {
            rep(i,0,upp)
                ret+=dfs(pos-1,-sign,lenp,valp,0,up && i==a[pos])+i*sign*dfs2(pos-1,valp,up && i==a[pos]);
        }
    }

    if(!z && !up && pos%2==lenp)dp[pos][sign+1][valp]=ret;
    return ret;
}

LL calc(LL x)
{
    int len=0;
    while(x)a[++len]=x%10,x/=10;
    LL d1=dfs(len,-1,0,0,1,1),d2=dfs(len,-1,0,1,1,1),d3=dfs(len,-1,1,0,1,1),d4=dfs(len,+1,1,1,1,1);
    // cout<<d1<<' '<<d2<<' '<<d3<<' '<<d4<<endl;
    return d1+d2+d3+d4;
}

void init()
{
    clr(dp,-1);
    clr(dp2,-1);
}

int main()
{
    init();
    LL n;
    while(~scanf("%lld",&n))
    {
        if(n==0)break;
        printf("%lld\n",calc(n));
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值