原题链接
考察:数位dp
这道题能用递推的方式实现吗...如果有请告知本蒟蒻(.)
错误思路:
建立dp数组f[pos][len][last]表示枚举到第pos位,目前长度为len,上一位是last的情况.如果我们枚举的i>last,len+1.如果i<=last,len不变,last不变.
WA数据:12534 这样计算它的最长上升子序列是3,实际是4.这道题每枚举一个非前导零的数字,不能像处理前导零那样无视过去,每一个数字一定会再数字字符串里起到作用,这样会统计到错误的最长子序列长度.
正确思路:
回想一下是怎么求最长上升子序列的,O(n2)的做法在这里基本不可能实现,考虑二分的做法.我们保留每个上升子序列长度的最小末尾数字.长度最多为10,考虑用二进制保存.
eg: 132 起始sta = 0
枚举到1: sta : 0--> 2(10)
枚举到3: sta ; 1--> 9(1010)
这里可以发现最长子序列长度等于1的个数.枚举到最后一位检验即可.
注意处理前导零的情况.
Code
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 21,M = 11;
int k,a[N];
LL f[N][1<<M][M];
int lowbit(int x)
{
return x&-x;
}
int get(int sta)
{
if(!sta) return k==1;
int res = 0;
for(int i=sta;i;i-=lowbit(i)) res++;
return res;
}
int calc(int st,int x)
{//sta表示的是长度为i(1的个数)的子序列中,末尾最小是 第i个为1的数
for(int i=x;i<10;i++)
if(st>>i&1) return ((st-(1<<i))|(1<<x));
return st|(1<<x);
}
LL dfs(int pos,int sta,bool limit,bool lead)
{//保证右分支加入后能算出正确答案
if(!pos) return get(sta)==k;
if(!limit&&!lead&&f[pos][sta][k]!=-1) return f[pos][sta][k];
int up = limit?a[pos]:9;
LL res = 0;
for(int i=0;i<=up;i++)//左分支 [0,up) [up,up]右分支
{
if(!i&&lead) res+=dfs(pos-1,sta,limit&&i==up,lead);
else res+=dfs(pos-1,calc(sta,i),limit&&i==up,lead&&!i);
}
if(!limit&&!lead) f[pos][sta][k] = res;
return res;
}
LL dp(LL n)
{
if(!n) return k==1;
int cnt = 0;
while(n) a[++cnt] = n%10,n/=10;
return dfs(cnt,0,1,1);
}
int main()
{
int T,kcase = 0;
scanf("%d",&T);
memset(f,-1,sizeof f);
while(T--)
{
LL l,r;
scanf("%lld%lld%d",&l,&r,&k);
printf("Case #%d: %lld\n",++kcase,dp(r)-dp(l-1));
}
return 0;
}