XHXJ's LIS HDU - 4352 (数位dp+状压+nlngn的最长上升子序列求法)

题意:求[l,r]区间里数字满足最长上升子序列为k的个数。

思路:数位dp

首先需要将含有那些数字转化成二进制状态,对于一个新的数位,利用nlogn的思路对状态进行更新,但是需要注意,需要判前导0,因为只有没有前导0的才是数字。

要点就是n*logn状态的写法,其实它的思想就是将最后一位尽可能的小,看代码应该可以理解。

思路:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
#include <set>
#include <string>
#include <math.h>
#include <stack>
typedef long long ll;
#define INF 0x3f3f3f3f
const int maxn=3e5+10;
const int MAXN=1e3+10;
using namespace std;
int a[30];
ll dp[30][1<<10][12];
int k;
int getnum(int x)
{
    int ans=0;
    while(x)
    {
        if(x&1)
        {
            ans++;
        }
        x>>=1;
    }
    return ans;
}
int getnew(int x,int s)//更新新的状态
{
    for(int i=x;i<10;i++)
        if(s&(1<<i))return (s^(1<<i))|(1<<x);//如果有比x大的数位,那么就用x来进行替换
    return s|(1<<x);
}
ll dfs(int pos,int sta,bool limit,bool ze)
{
    if(pos==-1)
    {
        return k==getnum(sta);
    }
    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++)
    {
        ans+=dfs(pos-1,(ze&&(i==0))?0:getnew(i,sta),limit&&i==up,ze&&(i==0));
    }
    if(limit==0)
    {
        dp[pos][sta][k]=ans;
    }
    return ans;
}
ll solve(ll x)
{
    int pos=0;
    while(x)
    {
        a[pos++]=x%10;
        x/=10;
    }
	return dfs(pos-1,0,1,1);
}
int main(int argc, char const *argv[])
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    int T;
    cin>>T;
	int Case=0;
	memset(dp,-1,sizeof(dp));
    while(T--)
    {
        ll l,r;
        //scanf("%lld%lld%d",&l,&r,&k);
		cin>>l>>r>>k;
		printf("Case #%d: ",++Case);
		cout<<solve(r)-solve(l-1)<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40774175/article/details/81675061