【HDU5542 2015 CCPC 南阳国赛C】【DP】The Battle of Chibi n个数中恰好长度为m的单升子序列数

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int Z=1e9+7;
const int N=1005;
int casenum,casei;
int n,m;
int a[N];
int f[N][N];
int d[N][N];int top;
#include<map>
map<int,int>mop;
map<int,int>::iterator it;
inline void add(int &x,int y)
{
	x+=y;
	if(x>=Z)x-=Z;
}
int cnt(int p,int x)
{
	long long tmp=0;
	for(;x;x-=x&-x)tmp+=d[p][x];
	return tmp%Z;
}
void add(int p,int x,int v)
{
	for(;x<=top;x+=x&-x)add(d[p][x],v);
}
int main()
{
	scanf("%d",&casenum);
	for(casei=1;casei<=casenum;casei++)
	{
		scanf("%d%d",&n,&m);
		mop.clear();
		for(int i=1;i<=n;i++)scanf("%d",&a[i]),mop[a[i]]=0;
		top=0;for(it=mop.begin();it!=mop.end();it++)it->second=++top;
		for(int i=1;i<=n;i++)a[i]=mop[a[i]];
		
		memset(d,0,(m+1)*N*4);
		memset(f,0,(n+1)*N*4);
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			int top=min(i,m);
			for(int j=1;j<=top;j++)
			{
				if(j==1)f[i][j]=1;
				else add(f[i][j],cnt(j-1,a[i]-1));
				add(j,a[i],f[i][j]);
			}
			add(ans,f[i][m]);
		}
		printf("Case #%d: %d\n",casei,ans);
	}
	return 0;
}
/*
【题意】
给你n(1e3)个数,每个数都在[1,1e9]范围。
然后让你保持数的顺序不变,选出长度恰好为m(1<=m<=n)的单调上升子序列。
问你有多少种选择方案

【类型】
DP
最长上升子序列
离散化+树状数组

【分析】
这道题一开始我是以O(nlogn)最长上升子序列的插入法做。但是很难实现。卡了一段时间。

后来结合n只有1000的情况,我们应该设想到O(n^2)的做法。
题目涉及到选择,我们不妨回归到一个很经典的DP模型——
用f[i][j]表示前i个数中选择了j个数的上升子序列的方案数。
这个状态的一个好处是,它把这个上升自序列的长度也包含了,就是j。
还有一个性质,我们如何查看一个数能否接在前面的序列后?其实只要知道之前上升子序列的最后一位就可以进行判定了。
鉴于这个,我们修改下f[i][j]的含义——
f[i][j]表示前i个数中选择了j个数,且最后一个数严格为a[i]时的上升子序列方案数。
这样就有状态转移f[i][j]=∑f[p][j-1],p∈[1,i-1]且a[p]<a[i]

因为我们可以严格保持i的升序,所以就只需要找到所有长度为j-1且尾节点a[p]<a[i]的子序列个数即可。
且因为for i for j已经使得复杂度变成O(n^2),所以这个操作要在log(n)级别的时间内完成。
而且这个操作设计到动态操作,于是我们想到树状数组(线段树也可)。

这样这道题的做法就很清晰了。
1,离散化
2,状态转移
for(int i=1;i<=n;i++)
{
	//其实这个j的顺序并不重要,因为我们更新的尾节点长度为a[i],自己是无法延伸而来的
	for(int j=top;j>=1;j--)
	{
		if(j==1)f[i][j]=1;
		else add(f[i][j],cnt(j-1,a[i]-1));
		add(j,a[i],f[i][j]);
	}
	add(ans,f[i][m]);
}

【时间复杂度&&优化】
O(n^2 logn)
取模还是很消耗时间的233

*/

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值