51nod 完美消除 DP

时间:2022-12-16 11:47:12

定义数的消除操作为选定[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

题解:表示这种题目一看就像套路题,好像模型很熟悉。但是并没有什么卵用,列不出dp式,后来去膜了一发题解才觉得这dp思路是真的巧妙。

我们首先思考给定一个数把它消成0的最小步数如何计算。就是从高位到低位枚举,设当前数字为x,开个栈,将栈里所有大于x的数字删除,如果此时栈里没有数字x则加入,并且答案+1。(经典套路,然而我并没想到)
然后把这个想法套到数位DP中,设f[i,s,k,c]表示前i位,栈中集合为s,已经做了k次操作,当前数与给定数的大小关系(0或1)。
然后就可以直接做了。

code(真的短小精悍= =)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
int n,m;
typedef long long ll;
int k,bit[20];
ll f[50][1050][20][2];
ll solve(ll n)
{
    memset(f,0,sizeof(f));
    int len=0;
    while (n)bit[++len]=n%10,n/=10;
    //reverse(bit+1,bit+1+len);
    f[0][1][0][0]=1;
    fo(i,1,len)
    fo(S,1,1023)
    fo(v,0,k)
    fo(c,0,1)
    {
        ll ans=f[i-1][S][v][c];
        if (!ans)continue;
        fo(y,0,9)if (y<=bit[i]||c)
        {
            int S2=S|(1<<y),k2=v+1;
            fo(j,y+1,9)
            if (S>>j&1)S2^=(1<<j);
            if (S>>y&1)k2--;
            f[i][S2][k2][c|(y<bit[i])]+=ans;
        }
    }
    ll ans=0;
    fo(S,1,1023)ans+=f[len][S][k][1];
    return ans;
}
int main()
{
    ll l,r;
    scanf("%lld%lld",&l,&r);
    scanf("%d",&k);
    printf("%lld\n",solve(r+1)-solve(l));
    return 0;
}