http://acm.hdu.edu.cn/showproblem.php?pid=4996
直接贴bc题解
按数字1-N的顺序依次枚举添加的数字,用2N 的状态保存在那个min数组中的数字,每次新添加数字可以根据位置计算出新的min数组。 怎么快速计算呢?这里如果枚举N的位置是不可行的,这样2n 的state记录的信息不够。很巧妙的思路是枚举放在当前位置的数字,比如说1-N-1的排列状态下,枚举第N位为K,那么1-N-1位的>=k 的数字全加1,就得到了一个1-N的排列。
当然这个算法是有问题的,但是由于求的是长度,所以所有j代表的状态并不能正确表示对应的上升序列,因为每个有值的dp[j]必然有j&1 == 1,但是却奇迹般的AC了..所以大致理解算法思想和AC原因但是这个算法的解释略有疑问...
#include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <string> #include <queue> #include <map> #include <iostream> #include <algorithm> using namespace std; #define RD(x) scanf("%d",&x) #define RD2(x,y) scanf("%d%d",&x,&y) #define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z) #define clr0(x) memset(x,0,sizeof(x)) typedef long long LL; const int maxn = 1<<20; int n,k; LL f[20][20],dp[maxn],tmp[maxn]; int cal(int x) { int ans = 0; for(int i = 0;i < 20;++i) if((1<<i)&x) ans++; return ans; } void init () { dp[1] = f[1][1] = 1; for(int i = 1;i < 18;++i){ for(int j = 0;j < (1<<i);++j) tmp[j] = dp[j]; for(int j = 0;j < (1<<(i+1));++j) dp[j] = 0; for(int j = 0;j < (1<<i);++j)if(tmp[j]){ for(int k = 0;k <= i;++k){ int tot = 0,c[20],st = 0; for(int l = 0;l < i;++l){ if((1<<l) & j){ c[tot] = l; if(c[tot] >= k) c[tot]++; tot++; } } c[tot++] = k; for(int l = 0;l < tot;++l){ if(c[l] > k){ c[l] = k; break; } } for(int l = 0;l < tot;++l){ st |= (1<<c[l]); } dp[st] += tmp[j]; } } for(int j = 0;j < (1<<(i+1));++j) f[i+1][cal(j)] += dp[j]; } } int main() { init(); int _;RD(_);while(_--){ RD2(n,k); printf("%I64d ",f[n][k]); } return 0; }