题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=4352
题意:在给定区间L-R内有多少数满足长度为K的最长上升子序列。
LIS算法可以事先去了解一下,eg: 3,5,8,9的序列,现在碰到了6,则对序列更新为3,5,6,9,
(LIS基于DP的思想,优化后是如上所示的贪心策略加二分,时间复杂度有n*n到n*logn);
又因为是由0-9的数字,转为二进制不超过2的10次幂(1024),对所有的进行二进制的状态压缩,更新思想也是基于LIS的贪心思想,
dp[i][j][k]表示第i位数,状态为j时的LIS为k的数量,以此去记录状态,每换新长度时都必须初始化状态因为是一个新的开始。
可以看代码具体实现。
first作用为判断是都前缀0,即可以理解为数字都没有被放入,limit即是限制数字大小。
update中 s(当前状态)&1<<i 即是比较有没有(2的i次幂)这一大小的的值,即有没有数字i,没有的话i++,继续向前找,如果有的话,s^1<<i 即找到大于等于x的(^即是异或运算),s^1<<i后(即消除它)或运算1<<x(当前的数放入),要是没有的话,直接s|1<<x(即x为最大的放入数字更新)。要是这个过程还不懂的话,自己模拟一下LIS算法的如上面的eg所示的例子,其实也就是思想。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<queue>
#define MAX_len 50100*4
using namespace std;
typedef long long ll;
const ll mod=1000000007;
ll k;
int a[70];
ll dp[30][1<<10][15];
int bit(int state)
{
int cnt=0;
while(state>0)
{
if(state&1==1)
cnt++;
state>>=1;
}
return cnt;
}
int update(int x,int s)
{
for(int i=x;i<10;i++)
{
if(s&(1<<i))
return (s^(1<<i))|(1<<x);
}
return s|(1<<x);
}
ll dfs(int pos,int sta,bool first,bool limit)
{
if(pos<1)
return bit(sta)==k;
if(!limit&&dp[pos][sta][k]!=-1)
return dp[pos][sta][k];
ll temp=0;
int up=limit?a[pos]:9;
for(int i=0;i<=up;i++)
{
temp+=dfs(pos-1,(first&&(i==0))?0:update(i,sta),first&&(i==0),limit&&i==up);
}
if(!limit)
dp[pos][sta][k]=temp;
return temp;
}
ll solve(ll n)
{
int len=1;
while(n)
{
a[len++]=n%10;
n/=10;
}
return dfs(len-1,0,1,true);
}
int main()
{
memset(dp,-1,sizeof(dp));
int T;
scanf("%d",&T);
int yy=1;
while(T--)
{
ll l,r;
scanf("%I64d %I64d %I64d",&l,&r,&k);
printf("Case #%d: ",yy++);
printf("%I64d\n",solve(r)-solve(l-1));
}
return 0;
}