hdu4352 XHXJ's LIS【数位dp】

题目大意:

定义f(i)表示将i看成字符串的最长上升子序列长度。
给定l,r,k,求满足l<=i<=r且f(i)=k的个数。
1<=l<=r<=10^18,1<=k<=10,T<=10000。

解题思路:

注意是最长上升序列,如果是不下降序列就无法做了。
回忆O(nlogn)求 LIS 的过程,维护一个上升序列,每次新加一个数的时候,用它去替换里面最小的大于它的数,最后序列长度就是答案。
对于本题,因为数位只可能是 0 到 9,所以可以考虑用一个10位二进制数来唯一确定这个需要维护的序列。
设f(i,j,lim,z)为从高到低填了前 i 位,之前部分的序列情况为j,是否封顶,前缀是否全为0的数字个数,然后dp即可。
注意询问组数很多,所以要保证dp记忆化具有通用性,不能每次重新dp。
时间复杂度O(logr*2^10*10)。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll getint()
{
    ll i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
int T,k,num[25];
ll l,r,dp[25][1<<10][11];
int get_num(int s)
{
    int res=0;
    for(int i=0;i<10;i++)res+=(s>>i&1);
    return res;
}
int get_s(int s,int x)
{
    for(int i=x;i<10;i++)
        if(s>>i&1)return (s^(1<<i))|(1<<x);
    return s|(1<<x);
}
ll dfs(int pos,int s,bool lim,bool z)
{
    if(pos==-1)return get_num(s)==k;
    if(!lim&&dp[pos][s][k]!=-1)return dp[pos][s][k];
    int end=lim?num[pos]:9;ll res=0;
    for(int i=0;i<=end;i++)
        res+=dfs(pos-1,(i==0&&z)?0:get_s(s,i),lim&&(i==end),z&&(i==0));
    if(!lim) dp[pos][s][k]=res;
    return res;
}
ll solve(ll n)
{
    int len=0;
    while(n)num[len++]=n%10,n/=10;
    return dfs(len-1,0,1,1);
}
int main()
{
    //freopen("lx.in","r",stdin);
    memset(dp,-1,sizeof(dp));
    T=getint();
    for(int t=1;t<=T;t++)
    {
        l=getint(),r=getint(),k=getint();
        printf("Case #%d: %lld\n",t,solve(r)-solve(l-1));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值