定义数的消除操作为选定[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
单组测试数据。 三个整数L、R和k(1<=L<=R<=10^18,1<=k<=18)。
一个整数表示答案。
10 21 2
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][i−1][K−t2] 为这类里符合要求的数的数量(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;
}