【dp方案数】【贪心】POJ1952

题面

给你一个长度为n的序列,求这个序列的最长下降子序列的长度以及方案数。1<=n<=5000

链接:http://poj.org/problem?id=1952

思路

很容易想到一个状态\(f_i,_j\)表示以\(a_i\)时,长度为j的方案数。

转移方程:\(f_i,_j\)=\(\begin{matrix} \sum_{k=j-1\,and\,a_k>a_i}^{i-1} \end{matrix} f_k,_{j-1}\)

但很明显,这是O\((n^3)​\),会超时。dp有一个很常用的优化就是舍去冗余状态,我们考虑对于到i为止的最长下降子序列,它只可能由到k(1<=k<j \(a_k​\) \(a_i​\))为止的最长的序列转移过来,我们所要求的,和要知道的都是最长的,所以\(f_i,_j​\)的第二维完全可以去掉,但同时我们还是需要知道长度,所以我们设\(f_i​\)表示以\(a_i​\)结尾的最长下降子序列的方案数,\(g_i​\)表示以\(a_i​\)结尾的最长下降子序列的长度。假设原序列的数两两不同,则有转移方程:

\(f_i=\begin{matrix} \sum_{k=1\,and\,g_k=g_i-1}^{i-1} \end{matrix} f_k\)

那现在数可能相同,我们考虑对于\(a_k\)=\(a_j\)>\(a_i\),k<j<i,\(f_k\)的范围被\(f_j\)包含,所以如果\(g_k\)=\(g_j\),那么f_k的方案数一定被包含在了f_j中,所以我们只要用f_j来转移f_i就可以啦,即对于所有相等的\(a_k\),k<i,我们只需要用最大的k来更新f_i就可以了,具体操作,可以倒序循环,扫到一个之前没有扫过的\(a_k\)的值,就做一个标记,以后再扫到时就跳过。在最后求最终答案时也要注意这一点。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5e3+10;
typedef long long ll;
ll f[N];
int g[N],a[N],vis[(1<<16)+10];
int main()
{
	int n,ans=0;
	ll cnt=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	{
		g[i]=f[i]=1;
		for(int j=i-1;j>=1;j--)
		{
			if(!vis[a[j]])
			{
				vis[a[j]]=1;
				if(a[j]<=a[i]||g[j]+1<g[i]) continue;
				if(g[j]+1>g[i]) g[i]=g[j]+1,f[i]=0;
				f[i]+=f[j];
			}
		}
		memset(vis,0,sizeof(vis));
	}
	for(int i=n;i>=1;i--)
	{
		if(g[i]>ans) ans=g[i],cnt=0;
		if(!vis[a[i]]&&g[i]==ans) cnt+=f[i];
		vis[a[i]]=1;
	}
	printf("%d %lld\n",ans,cnt);
	return 0;
}
posted @ 2019-06-09 22:05  FlashiLizard  阅读(138)  评论(0编辑  收藏  举报