HDU 4352 XHXJ's LIS ★(数位DP)

题意

求区间[L,R]内满足各位数构成的数列的最长上升子序列长度为K的数的个数。

思路

一开始的思路是枚举数位,最后判断LIS长度。但是这样的话需要全局数组存枚举的各位数字,同时dp数组的区间唯一性也被破坏了(我不知道MYQ10那题怎么被我用这种方法做对的。。。) 看了题解后发现了二进制缩位处理LIS的巧妙方法~~ 我们用一个长10位的二进制数state表示0~9之前是否出现过,而更新的时候也需要一点技巧:如果我们当前处理到的位数是i,那么我们就找state中不小于i的第一位非0位,把它置0,再把第i位置1。为什么这样处理呢?想想我们更新时的困难,就在于怎样维持state后面1的出现顺序要在前面1的后面,这样最后state中1的个数才表示LIS长度。呐当我们处理i时,对于i后面的1,就不符合出现顺序了,所以需要调整。但是我们发现对于紧接着i的那个1,它可以被i“替换”掉。因为此时以它为结尾和以i为结尾的LIS长度一样(因为是近邻着的下一个1),而它被i“替换”后又不影响了后面1的出现顺序了,把i当成原来的它就行了~ 还有一点是,相对于每次询问都初始化dp数组(超时),更好的方法是给dp加一维[11]表示处理的K,这样只需在最开始初始化即可。

代码

  [cpp] #include <iostream> #include <cstdio> #include <cmath> #include <algorithm> #include <string> #include <cstring> #include <vector> #include <set> #include <stack> #include <queue> #define MID(x,y) ((x+y)/2) #define MEM(a,b) memset(a,b,sizeof(a)) #define REP(i, begin, end) for (int i = begin; i <= end; i ++) using namespace std; typedef unsigned long long LL; typedef vector <int> VI; VI num; LL dp[25][2][1050][11]; LL L, R; int K; int getnewstate(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); } int get_dig_num(int state){ int res = 0; for (int i = 0; i <= 9; i ++){ if (state & (1<<i)) res ++; } return res; } LL dfs(int pos, bool zero, int state, bool limit){ if (pos == -1){ return (get_dig_num(state) == K); } if (!limit && ~dp[pos][zero][state][K]) return dp[pos][zero][state][K]; int end = limit?num[pos]:9; LL res = 0; for (int i = 0; i <= end; ++ i){ bool next_zero = (zero && i==0); res += dfs(pos-1, next_zero, next_zero?0:getnewstate(i, state), limit&&(i==end)); } return limit?res:dp[pos][zero][state][K]=res; } LL solve(LL x){ num.clear(); while(x){ num.push_back(x%10); x /= 10; } LL res = dfs(num.size()-1, 1, 0, true); return res; } int main(){ int t; MEM(dp, -1); scanf("%d", &t); REP(ca, 1, t){ scanf("%I64d %I64d %d", &L, &R, &K); printf("Case #%d: %I64d\n", ca, solve(R)-solve(L-1)); } return 0; } [/cpp]

你可能感兴趣的:(HDU)