您的位置:首页 > 其它

数位DP(数字和与倍数,uva 11361)

2016-11-09 11:49 344 查看
这道题拖了一整天,WA了无数发,感觉自己数位DP特别弱,其实可能就是因为自己数学比较弱,或者说在推导和代入繁杂的公式以及边界很粗糙的讨论上太不够严谨了,总是觉得大概对就对了,这在数学题上是非常致命的缺点,因为公式错一点点就直接WA,讨论一遗漏也直接WA。然而这道题真蛋疼,明明公式错了,然而样例甚至自己生成的大部分数据都能过。。。

数位DP还需要大量的练习,太不够熟练了。

然后是解题思路:

有一类题目,就是问你在[a,b]中有多少个满足条件的数。a,b很大,暴力枚举是不可能,只能通过记忆化搜索实现数位DP来求解。

基本思路就是设f(x)表示不超过x的非负整数中满足条件的个数。则原题目的答案等于f(b)-f(a-1)。

首先是状态的定义,既然是数位DP,那么第几位(有几位)必然是其中一维。

题目有两个要求,第一个是数本身是k的倍数,第二个是各个数字之和也是k的倍数。这两个要求无必然联系,必须要同时确定才能确定状态。

因此状态为3维,dp(d,m1,m2)表示第i为(有i位),对k求余为m1,各位数和对k求余为m2的数的个数。

那么就枚举x从0到9状态转移方程就是dp[d][m1][m2]=sum(dp[d-1][((m1-x)%k+k)%k][((m2-x*10^(d-1))%k+k)%k])。

表示这个状态是由第d位放x,然后考虑d-1位的情况数。x取0~9,求和就是这个状态的值。

一个技巧就是如果希望a%b的值取到正数,那么就取(a%b+b)%b。

然后答案就是从高位往低位枚举,从0往bit[i]枚举,注意每一轮枚举别超了,最后别漏了刚好等于最大值的情况。

最后还有一些值得注意的地方,一开始我开了一个数组用来记忆化搜索,然后k太大了,有1e4,位数只有10。所以dp[15][1e4+10][1e4+10]根本就开不下。然后就想会不会有很多状态用不到,就决定改用哈希表。最后错在了公式= =。

事实上转哈希表这个方法确实不错,但是有一个更加显然的优化方法,那就是k大于100的时候都是无解的啊,那么只用开dp[15][110][110]就够了,大于100的时候直接输出0就好啦。我只是无脑转哈希表,也没仔细想到底是哪些状态不会用到,以为是状态离散的。事实上不能到达的状态是大量且连续的,所以直接开小数组+特判就OK。以后要多注意这种特殊情况,以优化空间和时间。

还有就是数位dp要注意特判的位置以及0的特判。

最好写在f函数里。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

/////////////////////////////////////
//hash
const ll hashsize=10000;
const ll maxstate=1000000;
ll head[hashsize];
struct node
{
ll d,m1,m2,val,next;
}Node[maxstate];
ll tot;
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
ll get(ll d,ll m1,ll m2)
{
ll h=(d+m1+m2)%hashsize;
for(ll i=head[h];i!=-1;i=Node[i].next)
if(d==Node[i].d&&m1==Node[i].m1&&m2==Node[i].m2)
return Node[i].val;
return -1;
}
ll add(ll d,ll m1,ll m2,ll val)
{
ll h=(d+m1+m2)%hashsize;
Node[tot].d=d;
Node[tot].m1=m1;
Node[tot].m2=m2;
Node[tot].val=val;
Node[tot].next=head[h];
head[h]=tot++;
return val;
}
///////////////////////////////////

ll mp(ll x,ll n)
{
ll ret=1;
while(n)
{
if(n&1) ret*=x;
x*=x;
n>>=1;
}
return ret;
}

ll f(ll d,ll m1,ll m2,ll K)
{
ll ans=get(d,m1,m2);
if(ans!=-1) return ans;
if(!d)
{
if(m1==0&&m2==0) return add(d,m1,m2,1);
else return add(d,m1,m2,0);
}
ans=0;
ll POW=mp(10,d-1);
for(ll x=0;x<10;x++)
ans+=f(d-1,((m1-x)%K+K)%K,((m2-x*POW)%K+K)%K,K);
return add(d,m1,m2,ans);
}

ll g(ll X,ll K)
{
if(!X||K>90) return 1;
ll ans=0;
ll len=0;
ll bit[15];
while(X)
{
bit[++len]=X%10;
X/=10;
}
ll tou=0;
ll sum=0;
for(ll i=len;i;i--)
{
tou*=10;
ll POW=mp(10,i-1);
for(ll j=0;j<bit[i];j++)
{
ans+=f(i-1,((-sum)%K+K)%K,(((-tou-j)*POW)%K+K)%K,K);
sum++;
}
tou+=bit[i];
};
ans+=f(0,((-sum)%K+K)%K,((-tou)%K+K)%K,K);
return ans;
}

int main()
{
ll T;
scanf("%lld",&T);
while(T--)
{
init();
ll A,B,K;
scanf("%lld %lld %lld",&A,&B,&K);
printf("%lld\n",g(B,K)-g(A-1,K));
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: