Sweety

Practice makes perfect

导航

SPOJ - BALNUM Balanced Numbers (*数位DP+三进制拆分)

Posted on 2017-04-30 19:50  蓝空  阅读(176)  评论(0编辑  收藏  举报




Balanced numbers have been used by mathematicians for centuries. A positive integer is considered a balanced number if:

1)      Every even digit appears an odd number of times in its decimal representation

2)      Every odd digit appears an even number of times in its decimal representation

For example, 77, 211, 6222 and 112334445555677 are balanced numbers while 351, 21, and 662 are not.

Given an interval [A, B], your task is to find the amount of balanced numbers in [A, B] where both A and B are included.

Input

The first line contains an integer T representing the number of test cases.

A test case consists of two numbers A and B separated by a single space representing the interval. You may assume that 1 <= A <= B <= 1019 

Output

For each test case, you need to write a number in a single line: the amount of balanced numbers in the corresponding interval

Example

Input:
2
1 1000
1 9
Output:
147
4


这题要求出现的数字,偶数出现奇数次,奇数出现偶数次。(注意是每一个偶数都出现奇数次)

用三进制表示0~9的状态

相信都对二进制拆分很熟悉,但是这种三进制拆分的方式还是比较巧妙的,其实开始的时候还想用二进制拆分的方式也就是用两位二进制表示,这样的话就需要2^20的数组空间了,明显超限了,所以最后发现这种三进制拆分的方式还是很好的,值得借鉴。

很好的数位DP问题有木有大笑


#include <bits/stdc++.h>
#define LL long long
using namespace std;

LL dp[20][59049+100];///dp[i][j][k]:i:处理的数位,j:偶数个数,k奇数个数 3^10
int bit[20];

bool check(int num){
   for(int i=0; num!=0 ;i++){
      if(i%2==1 && num%3==1) return false;
      if(i%2==0 && num%3==2) return false;
      num/=3;
   }
   return true;
}
int ppow(int x,int upper){
    int tmp=1;
   while(upper--)
       tmp*=x;
   return tmp;
}
int amend(int num,int i){
    int state = num/(int)pow(3,i)%3;
    if(state==0 ||state == 1)   num=num+pow(3,i)+0.5;///加0.5是因为精度损失了,也可以直接对pow强制int类型转换
    else if(state == 2) num=num-pow(3,i)+0.5;        ///也可以用我上面自己写的ppow
    return num;
}

LL dfs(int pos,int num,bool zeroflag , bool flag)///分别表示当前位置,三进制拆分组合后的数,前面是否出现过非零的数(用于统计0),和数位限制
{
    if(pos==-1)     return check(num);
    if(!flag && dp[pos][num]!=-1)
        return dp[pos][num];


    int end = flag ? bit[pos] : 9;
    LL ans=0;
    for(int i=0;i<=end;i++){
          ans+=dfs(pos-1, zeroflag||i?amend(num,i):0 , zeroflag||i, flag&&(i==end));
    }


    if(!flag) dp[pos][num]=ans;
    return ans;
}

LL calc(int n)
{
    int pos=0;
    while(n)
    {
        bit[pos++]=n%10;
        n/=10;
    }
    return dfs(pos-1,0,0,1);
}

int main()
{
    //double a=(0x3f3f3f3f);
   // printf("%.10f %d",a,(0x3f3f3f3f));
    memset(dp,-1,sizeof(dp));
    int m,n,T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&m,&n);
        printf("%lld\n",calc(n)-calc(m-1));
       // cout<<"=======\n";
    }
    return 0;
}


下面是网上比较流行的代码,不过这个getnews有点丑,还是贴一下,毕竟自己还是在这个pow上吃了苦头的!!!

//============================================================================
// Name        : SPOJ.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
long long dp[20][60000];
//3进制表示数字0~9的出现情况,0表示没有出现,1表示奇数次,2表示偶数次
int bit[20];
bool check(int s)
{
    int num[10];
    for(int i=0;i<10;i++)
    {
        num[i]=s%3;
        s/=3;
    }
    for(int i=0;i<10;i++)
        if(num[i]!=0)
        {
            if(i%2==0 && num[i]==2)return false;
            if(i%2==1 && num[i]==1)return false;
        }
    return true;
}
int getnews(int x,int s)
{
    int num[10];
    for(int i=0;i<10;i++)
    {
        num[i]=s%3;
        s/=3;
    }
    if(num[x]==0)num[x]=1;
    else num[x]=3-num[x];
    int news=0;
    for(int i=9;i>=0;i--)
    {
        news*=3;
        news+=num[i];
    }
    return news;
}
long long dfs(int pos,int s,bool flag,bool z)
{
    if(pos==-1)return check(s);
    if(!flag && dp[pos][s]!=-1)
        return dp[pos][s];
    long long ans=0;
    int end=flag?bit[pos]:9;
    for(int i=0;i<=end;i++)
        ans+=dfs(pos-1,(z&&i==0)?0:getnews(i,s),flag&&i==end,z&&i==0);
    if(!flag)dp[pos][s]=ans;
    return ans;
}
long long calc(long long n)
{
    int len=0;
    while(n)
    {
        bit[len++]=n%10;
        n/=10;
    }
    return dfs(len-1,0,1,1);
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int T;
    memset(dp,-1,sizeof(dp));
    long long a,b;
    scanf("%d",&T);
    while(T--)
    {
        cin>>a>>b;
        cout<<calc(b)-calc(a-1)<<endl;
    }
    return 0;
}



之前理解错了题意,以为是在整个数里面偶数的个数是奇数个,奇数的个数是偶数个,但是题目原意是每一个奇数或者偶数自己计数,每一个数都需要符合条件,自己的这个理解水平也是醉了,不过自己也是给自己出了一道不错的题目,也算是数位DP的了吧!不过比原意应该简单。

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

int dp[20][20][20];///dp[i][j][k]:i:处理的数位,j:偶数个数,k奇数个数
int bit[20];

int dfs(int pos,int even,int odd,bool zeromark,bool flag)
{
    cout<<"pos="<<pos<<" even="<<even<<" odd="<<odd<<endl;
    if(pos==-1) return even%2 && !(odd%2);
    if(!flag && dp[pos][even][odd]!=-1)
        return dp[pos][even][odd];



    int end = flag ? bit[pos] : 9;
    int ans=0;
    bool newzeromark , neweven;
    for(int i=0;i<=end;i++){
          newzeromark = (zeromark||i) ?  1 : 0;
          neweven = newzeromark ? even +((i%2==0) ? 1: 0) : even;
          ans+=dfs(pos-1, neweven, odd+(i%2) ,newzeromark , flag&&(i==end));
    }
   cout<<"pos="<<pos<<" even="<<even<<" odd="<<odd<<" flag="<<flag<<" ans="<<ans<<endl;



    if(!flag) dp[pos][even][odd]=ans;
    return ans;
}

int calc(int n)
{
    int pos=0;
    while(n)
    {
        bit[pos++]=n%10;
        n/=10;
    }
    return dfs(pos-1,0,0,0,1);
}

int main()
{
    memset(dp,-1,sizeof(dp));
    int m,n,T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&m,&n);
        printf("%d\n",calc(n)-calc(m));
        cout<<"=======\n";
    }
    return 0;
}