题意:定义一个数字的LIS为将其看成一个字符串的LIS,问\([l,r]\)内有多少数字的LIS长度为\(k\)
考虑树状数组求一个序列LIS的过程,我们定义了一个东西\(f_i\)表示结尾元素不超过\(i\)的LIS的最大长度,维护这个数组就能实现较为快速的转移
显然这个数组是单调不降的,且不难发现\(f_i-f_{i-1}\leq 1\),于是差分之后就只会存在\(0,1\)两种元素
于是我们在数位dp的过程中把差分序列记下来作为dp的状态就好了,注意前导零的问题,以及预处理转移会更快
代码
#include<cstdio>
#include<cstring>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
LL L,R;int k,m,lm[66];
int id[(1<<10)+5],st[11][(1<<10)+5],cnt[(1<<10)+5];
LL dp[2][(1<<10)+5][2];
int A[(1<<10)+5][11],tr[(1<<10)+5][11],B[11],M[11];
inline void trans(int state) {
for(re int i=0;i<10;++i)A[m][i]=state>>i&1;
for(re int i=1;i<10;++i)A[m][i]+=A[m][i-1];
}
inline void Pre_tr(int x) {
for(re int i=0;i<10;i++) {
for(re int j=0;j<10;j++)B[j]=A[x][j];
B[i]=max(B[i],(i>0?A[x][i-1]:0)+1);
for(re int j=1;j<10;j++)B[j]=max(B[j-1],B[j]);
tr[x][i]=B[0];
for(re int j=1;j<10;j++) tr[x][i]|=((B[j]-B[j-1])<<j);
tr[x][i]=id[tr[x][i]];
}
}
void dfs(int x,int state,int res) {
if(res<0) return;
if(x==10) {
id[state]=++m;trans(state);
cnt[m]=res;st[res][++M[res]]=m;
return;
}
dfs(x+1,state,res);
dfs(x+1,state|(1<<x),res+1);
}
inline LL calc(LL N) {
if(!N)return 0;int tot=0;
while(N) lm[++tot]=N%10ll,N/=10ll;
int o=0;memset(dp,0,sizeof(dp));
for(re int i=tot;i;--i,o^=1) {
dp[o][id[0]][i==tot?1:0]++;
for(re int j=1;j<=m;j++) dp[o^1][j][0]=dp[o^1][j][1]=0;
for(re int j=1;j<=m;j++) {
if(!dp[o][j][0]) continue;
if(cnt[j]>=k) continue;
for(re int k=0;k<10;++k)
if(tr[j][k]) dp[o^1][tr[j][k]][0]+=dp[o][j][0];
}
for(re int j=1;j<=m;j++) {
if(!dp[o][j][1]) continue;
if(cnt[j]>=k) continue;
for(re int k=0;k<lm[i];++k)
if(tr[j][k]) dp[o^1][tr[j][k]][0]+=dp[o][j][1];
if(tr[j][lm[i]]) dp[o^1][tr[j][lm[i]]][1]+=dp[o][j][1];
}
dp[o^1][tr[id[0]][0]][lm[i]?0:1]--;
}
LL ans=0;
for(re int i=1;i<=M[k];i++) ans+=dp[o][st[k][i]][0]+dp[o][st[k][i]][1];
return ans;
}
int main() {
m=0;dfs(0,0,0);
for(re int i=1;i<=m;i++) Pre_tr(i);
int T;scanf("%d",&T);
for(re int tnum=1;tnum<=T;++tnum) {
scanf("%lld%lld%d",&L,&R,&k);
printf("Case #%d: %lld\n",tnum,calc(R)-calc(L-1));
}
return 0;
}
之后这玩意就T没了,原因是\(T\leq 10^4\),实在是不小;于是改成记忆化才行
#include<cstdio>
#include<cstring>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
LL L,R;int k,m,lm[66];
int id[(1<<10)+5],cnt[(1<<10)+5];
LL dp[20][(1<<10)+5][11];
int A[(1<<10)+5][11],tr[(1<<10)+5][11],B[11],M[11];
inline void trans(int state) {
for(re int i=0;i<10;++i)A[m][i]=state>>i&1;
for(re int i=1;i<10;++i)A[m][i]+=A[m][i-1];
}
inline void Pre_tr(int x) {
for(re int i=0;i<10;i++) {
for(re int j=0;j<10;j++)B[j]=A[x][j];
B[i]=max(B[i],(i>0?A[x][i-1]:0)+1);
for(re int j=1;j<10;j++)B[j]=max(B[j-1],B[j]);
tr[x][i]=B[0];
for(re int j=1;j<10;j++) tr[x][i]|=((B[j]-B[j-1])<<j);
tr[x][i]=id[tr[x][i]];
}
}
void dfs(int x,int state,int res) {
if(res<0) return;
if(x==10) {
id[state]=++m;trans(state);cnt[m]=res;
return;
}
dfs(x+1,state,res);
dfs(x+1,state|(1<<x),res+1);
}
LL DFS(int len,int lim,int s) {
if(!len||cnt[s]>k) return cnt[s]==k;
if(!lim&&dp[len][s][k]!=-1) return dp[len][s][k];
LL ans=0;
for(re int i=0;i<=(lim?lm[len]:9);++i)
ans+=DFS(len-1,lim&&(i==lm[len]),tr[s][i]);
if(!lim) dp[len][s][k]=ans;
return ans;
}
inline LL calc(LL N) {
if(!N)return 0;int tot=0;
while(N) lm[++tot]=N%10ll,N/=10ll;
return DFS(tot,1,id[0]);
}
int main() {
m=0;dfs(0,0,0);memset(dp,-1,sizeof(dp));
for(re int i=1;i<=m;i++) Pre_tr(i);tr[id[0]][0]=id[0];
int T;scanf("%d",&T);
for(re int tnum=1;tnum<=T;++tnum) {
scanf("%lld%lld%d",&L,&R,&k);
printf("Case #%d: %lld\n",tnum,calc(R)-calc(L-1));
}
return 0;
}
//g++ hdu4352.cpp -o hdu4352