digit dp

ac1068

这题说的是给了一个区间计算这个区间内 数各个数字之和为S的最小数  

其实这个题目首先可以求出[A,B]内所有数字之和为S的数的个数cnt,然后观察一下,不难发现,最小的那个数字肯定是在 cnt=1的时候对应的区间端点。由于具有严格的单调性,即随着区间长度的延长,满足条件的cnt肯定会越来越多。所以先可以数位dp求出cnt。然后二 分区间,若cnt>=1,那么说明[A,B]间肯定不止一个符合条件的数字。注意题目要求最小的数字,所以二分区间右端点即可,不要二分左端。这是 主要思路。其实就是个裸的数位dp。

 上面这段话纯属 抄袭  第一次接触数位dp 感觉好神奇

这样说说我的理解  dp[i][j] 表示 第 i 位(这里高位放在尾部) 在前面不管有几位的状态下得到j值 到达第i位时 第i位 该点为最高位 所能形成和为S-j的数有多少个  也等价于记忆化搜索

这里感觉 数位dp这里的 limt 用的很巧 为了判断这个数字是否可以取到极限

#include <iostream>
#include <cstdio>
#include<string.h>
using namespace std;
const int maxn=200;
long long  dp[50][maxn],digit[50],a,b,s;
long long dfs(int pos,int per,int limt){

        if(pos==-1) return per==s;
        if(limt==0&&dp[pos][per]!=-1)return dp[pos][per];
        long long ret=0,ed=limt?digit[pos]:9;
        for(int i=0;i<=ed;++i){
            ret+=dfs(pos-1,per+i,limt&&i==ed);
        }
        if(limt==0) return dp[pos][per]=ret;
        return ret;
}
long long work(long long G){
     int len=0;
     while(G){
        digit[len++]=G%10;
        G=G/10;
     }
   return  dfs(len-1,0,1);
}
bool jud(long long L,long long R){

   return work(R)-work(L)>=1LL;
}
void solve(){
    long long L=a,R=b,mid,L1=a,ans;
    while(L<=R){
        mid=(L+R)/2;
        if(jud(L1,mid)){
            ans=mid;
            R=mid-1;
        }
        else L=mid+1;
    }
    printf("%lld\n",ans);
}
int main()
{
    while(scanf("%lld%lld%lld",&a,&b,&s)==3){
       memset(dp,-1,sizeof(dp));
       solve();
    }
    return 0;
}
View Code

 ac1122

于是喵喵对于选定的一个数 N , 她把所有 [0 , N] (闭区间) 中的数依据如下算法重新排列了一下

operator (int A ,  int B){
    if (A 的 二进制表示中 1 的个数 > B 的 二进制表示中 1 的个数)  那么 A 比 B 大;
    else if (A 的 二进制表示中 1 的个数 < B 的 二进制表示中 1 的个数) 那么 B 比 A 大;
    else if (A < B) 那么 B 比 A 大;
    else if (A > B) 那么 A 比 B 大;
    else A 和 B 相等
}

比如 N = 8 的时候,数字的顺序是

0,1,2,4,8,3,5,6,7

由此,她想知道 对于 给定的 N ,第 M 小的数是几,前 M 小的数和是多少。(注意,第 0 小的一定是 0 , 第 1 小的才是 1)。

用数位cnt 计算出 cnt[pos][per] 在第pos位的时候还剩下per个1 的 情况有多少种 同样用 数位dp 计算出dp[pos][per]第pos位还剩下per个1的且比我们要求的N小的得和油多少位,在用数位dp 不断的逼近一个值,使得这个值趋近于我们想要的,每次用cnt 去估算逼近这个值

#include <iostream>
#include <cstdio>
#include <string.h>
using namespace std;
typedef long long LL;
const LL mod = 1000000009;
LL dp[70][70],N,M;
LL cnt[70][70],Bin[70];
int digit[70];
int FindNum(LL a){
     int len = 0;
     while(a){
        digit[len++]=a&1LL;
        a=a>>1;
     }

     Bin[0]=1;
     for(int i =1; i<=len ;++ i)
        Bin[i]=Bin[i-1]<<1;
        return len-1;
}
LL dfscnt(int pos,int per,bool e){
      if(pos == -1)return per==0;
      if(e==false && cnt[pos][per]!=-1) return cnt[pos][per];
      LL res=0;
      int en = e?digit[pos]:1;
      for(int i = 0; i<=en ; ++ i)
        if(per-i>=0){
          res+=dfscnt(pos-1,per-i,e&&i==en);
      }
      return e?res:(cnt[pos][per]=res);
}
LL dfsdp(int pos, int per,bool e){

         if(pos == -1)return 0;
         if(e==false && dp[pos][per] != -1)return dp[pos][per];
         int en=e?digit[pos]:1;
         LL res = 0;
         for(int i=0; i<=en ;++ i)
         if(per-i>=0){
           LL loc= dfscnt(pos-1, per-i, e&&(en==i));
           LL temp= 0 ;
           if(i) temp=((Bin[pos]%mod)*((loc)%mod))%mod;
           res=(res+temp)%mod;
           res=(res+dfsdp(pos-1, per-i, e&&(en==i)))%mod;
         }
         return e?res:(dp[pos][per] = res);
}
LL dfsfind(int pos, int per, LL m, bool e){

         if(pos == -1) return m;
         int en = e?digit[pos]:1;
         for( int i= 0; i<=en; ++ i)
         if(per-i>=0){
             LL  k= dfscnt(pos-1,per-i,i==en&&e );
             if(k>=M) {
                    return dfsfind(pos-1, per-i, m|(1LL*i*Bin[pos]), e&&i==en );
             }
             M-=k;
         }
}
void solve(){
      if( M == 0){
        printf("0 0\n");
        return ;
      }
      LL ans=0;
      int head=1;
      int len = FindNum(N);
      for(;;++head){
          LL res = dfscnt(len,head,true);
          if(res >= M) break;
          M -= res;
          LL R = dfsdp(len, head, true);
          ans=(ans+R)%mod;
      }
      LL E=0;
      E = dfsfind(len, head, E, true);
      len=FindNum(E);
      ans= (ans+dfsdp(len,head,true))%mod;
      printf("%lld %lld\n",E,ans);
}
int main()
{
       int cas;
       scanf("%d",&cas);
       while(cas--){
            scanf("%lld%lld",&N,&M);
            memset(dp,-1,sizeof(dp));
            memset(cnt,-1,sizeof(cnt));
            solve();
       }
       return 0;
}
View Code

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2014-05-03 15:44  来自大山深处的菜鸟  阅读(347)  评论(0编辑  收藏  举报