XMOJ 1519-二分+数位dp

这题二分不好想,但是前面数位dp统计个数其实是蛮经典的吧。。

用dp[pos][pre]代表当前处理到pos位置处,前面个位数的和为pre满足条件的最终数字的个数,当递推到pos==-1处时,看此时的pre即所有位数的结果之和是否等于s,等于的话返回dfs的结果就是1,最终结果就加上这个数,不然的话dfs结果就是0,solve函数也很好懂吧,从最高位一位一位向下推就行。设计好状态很关键。

然后我们求出这个第一问后,写个函数判断当前区间符合条件的数是不是超过了1,区间是[A,B],你缩小为[A,mid],【前提是判断函数返回值为真】,但只能二分区间右端,左端不动,笔者在这里仅给出提示:因为我们将来想得到满足条件的最小的数。然后就是写代码了。

这题T了无数次,先是二分写错了,最后竟然是数据类型写错了,sigh。


/****************************
* author:crazy_石头
* date:2014/04/26
* time: 8 ms 
* algorithm:数位dp+二分 
* Pro:XMU 1519
***************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

#define INF INT_MAX
#define eps 1e-8
#define A system("pause")
#define rep(i,h,n) for(int i=(h);i<=(n);i++)
#define ms(a,b) memset((a),(b),sizeof(a))
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define LL long long
const int maxn=250+5;
const int maxm=30;
LL dp[maxn][maxn],digit[maxn],l,r,s;

inline LL dfs(LL pos,LL pre,LL limit)
{
       if(pos==-1) return pre==s;
       if(!limit&&~dp[pos][pre]) return dp[pos][pre];
       LL ret=0,end=limit?digit[pos]:9;
       for(int i=0;i<=end;i++)
       {
               int new_num=pre+i;
               ret+=dfs(pos-1,new_num,limit&&i==end);
       }
       if(!limit) return dp[pos][pre]=ret;
       return ret;
}

inline LL solve(LL n)
{
      LL len=0;
      while(n)
      {
         digit[len++]=n%10;
         n/=10;
      } 
      return dfs(len-1,0,1);//limit等于1表示后继状态未搜完,等于0表示后继状态已经搜完了;答案放进dp数组就行; 
}

inline bool ok(LL L,LL R)
{
       return (solve(R)-solve(L-1))>=1;
}

inline void test(LL L,LL R)
{
      LL ans,L1=L;
      while(L<=R)
      {
          LL mid=(L+R)>>1;
          if(ok(L1,mid)) 
          {
              ans=mid;
              R=mid-1;
          }
          else L=mid+1;
      }
      printf("%lld\n",ans);
}
          
int main()
{
    ms(dp,-1);
    scanf("%lld%lld%lld",&l,&r,&s);
    printf("%lld\n",solve(r)-solve(l-1));
    test(l,r);
    //A;
    return 0;
}

你可能感兴趣的:(ACM_dp)