XHXJ's LIS HDU - 4352

原题链接
考察:数位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;
}
posted @ 2021-05-30 15:17  acmloser  阅读(38)  评论(0编辑  收藏  举报