您的位置:首页 > 其它

POJ 1952 DP LIS求方案数

2015-09-04 08:51 316 查看
题意:给一个序列,求最长不上升子序列长度以及有多少个这样的子序列。注意:看起来相同的子序列被算作一种方案。例如:421421的方案为442,441,422,421,411五种。

对于需要求方案数的LIS,求长度时就老老实实用O(n^2)的DP算法吧。
f[i]表示以i为最后一个数的最长不上升子序列,if(a[i] <= a[j]) f[i] = max(f[i], f[j]+1),而max{f[i]}就是第一问的答案。这个很好说。

主要是方案数,题目要求看起来相同的是被算作一种的,我们先设d[i][j]为以i结尾,不上升子序列的长度为j的方案数。如果没有之前那个要求,那么d[i][j] = ∑ d[k][j-1](k < i && a[i] <= a[j])。
但是考虑到题目的要求,我们需要进行判重,可以这么想:在一个序列a1,a2……an中,如果有ai == aj(i < j),那么d[j][t]肯定包括所有的d[i][t](0<t<=f[i])。也就是说我们对每个数本身进行判重,对于k,需要逆序枚举,如果枚举过将vis[a[k]]标记为1,我们只讲vis[a[k]] == 0的加到d[i][j]中即可,题目中也说明了数的大小是1<<16以内的,所以bool型数组是开得下的。
另外需要注意,在递归的过程中,如果d[i1][j1]枚举过k,把vis[a[k]]设为1,但是在另一个状态d[i2][j2](i2>k,i1>k)中,a[k]这个数是可以被再次访问的,就是说,对于每个d[i][j]都有独立的一个vis数组,但是在递归中是开不下的,会RE,所以就需要一个小技巧:每次递归开始就memset一下,相当于新声明了一个vis数组,当需要d[i][j] += d[k][j-1]时,先递归访问dp(k,j-1),再将vis[a[k]]设为1即可。
最后输出答案是d[n+1][ans+1],我们假定a[n+1]是一个最小的数,最长不上升子序列长度加1。
或者直接写个循环,把长度为ans的累加即可,这个与dp(n+1,ans+1)这一层递归是等价的。

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

int n, ans, a[5005], f[5005], d[5005][5005];
bool vis[(1<<16)+5];

int dp(int i, int j){
memset(vis, 0, sizeof vis);
if(d[i][j]) return d[i][j];
if(j == 1) return d[i][j] = 1;
for(int k = i-1; k; k--){
if(!vis[a[k]] && a[k] > a[i] && f[k] == j-1)
d[i][j] += dp(k, j-1), vis[a[k]] = 1;
}
return d[i][j];
}

int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", a+i);
f[i] = 1;
for(int j = 1; j < i; j++)
if(a[j] > a[i]) f[i] = max(f[i], f[j]+1);
ans = max(ans, f[i]);
}
printf("%d %d", ans, dp(n+1, ans+1));
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: