51nod 1623 完美消除


基准时间限制:1 秒 空间限制:131072 KB 分值: 80  难度:5级算法题
 收藏
 关注

定义数的消除操作为选定[L,R,x],如果数的第L到第R位上的数字都大于等于x,并且这些数都相等,那么该操作是合法的(从低位到高位编号,个位是第一位,百位是第二位……),然后将这些位数上的数减x;否则就是不合法的,不能进行操作。对一个数操作最少的次数使得这个数变成0,这个操作次数称为该数的最小操作数。如:1232的最小操作数为3,一个合法解是[2,2,1],[1,3,2],[4,4,1]。

求L~R中最小操作数为k的数的个数。


例如:132,需要操作3次才能变为0。而131131 => 111131 => 111111 =>0

Input
单组测试数据。
三个整数L、R和k(1<=L<=R<=10^18,1<=k<=18)。
Output
一个整数表示答案。
Input示例
10 21 2
Output示例
9


sol:

转自解题报告:

对于一个数计算最小操作数:
维护一个栈。
从高位到低位依次考虑每一位,设当前数字为x,将栈里所有大于x的数字删除,如果此时栈里没有数字x则加入,并且答案+1。
用一个二进制数来表示栈里有哪些元素。
因为可以按位考虑所以可以用数位dp来做这个问题。
最后答案等于1~R里符合要求的数量-1~L-1里符合要求的数量。所以这里只考虑1~R的计算。
设数字R有n位,a[i]表示第i位。(从低位到高位编号,个位是第一位,百位是第二位……)
设某数第i位的数字为x,且x<a[i],且第i+1位~第n位数字与R相同,将这个数记为(i,x),
可以通过这个标准将数分类,最多18*9种分类。
对于一种分类,统计这种分类里有几个数符合题目要求。
计算出此时栈的状态和已累加的答案(即最小操作数),记为 t1,t2 , dp[t1][i1][Kt2] 为这类里符合要求的数的数量(K为输入要求的最小操作数)。
 dp[i][j][k] 表示当前栈里的元素状态为i,再填j位数字,使答案(即最小操作数)刚好再加k的方案数,这个可以通过枚举下一位数字转移得到。

总复杂度O(18*512*18*10)


code:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

typedef long long ll;
const int maxn = 50;
const int mod = 1e9+7;

ll dp[20][1<<10][20];
int a[maxn];
int K;

ll update(ll sta,int x){
    int sum=(1<<10)-1;
    for(int i=x+1;i<=9;i++){
        sta&=(sum-(1<<i));
    }
    sta|=(1<<x);
    return sta;
}

ll dfs(int pos,int sta,int k,bool limit){
//      cout<<pos<<' '<<sta<<' '<<k<<' '<<limit<<endl;
      if(pos==-1) {
        if(k==0) return 1;
        else return 0;
    }
    if(!limit&&dp[pos][sta][k]!=-1) return dp[pos][sta][k];
    int up=limit?a[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        int S=update(sta,i);
//        cout<<"------\n";
//        cout<<"i= "<<i<<" old "<<sta<<" new "<<S<<endl;
        if(sta&(1<<i)) ans+=dfs(pos-1,S,k,limit&&(i==up));
        else ans+=dfs(pos-1,S,k-1,limit&&i==up);
    }
    if(!limit) dp[pos][sta][k]=ans;
    return ans;
}

ll sol(ll x,int k){
    int pos=0;
    while(x){
        a[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,1,k,true);
}

int main(){
//    freopen("ou.txt","w",stdout);
    memset(dp,-1,sizeof(dp));
    ll l,r;
    int k;
    scanf("%lld%lld%d",&l,&r,&k);
    ll ans=sol(r,k)-sol(l-1,k);
    printf("%lld\n",ans);

    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值