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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章