洛谷 UVA12983 题解

22 篇文章 0 订阅

题目大意是在一个长度为 n n n 的数字序列中找到长度为 m m m 的严格上升子序列的个数。

注意,要找的是子序列的个数,并不是子串的个数。

思路

动态规划 + + + 树状数组维护。

我们设 f ( i , j ) f(i,j) f(i,j) 表示以 i i i 结尾,长度为 j j j 的严格上升子序列数,则:

  • j j j 1 1 1 时, f ( i , j ) = 1 f(i,j)=1 f(i,j)=1

  • j j j 不为 1 1 1 时, f ( k , j − 1 ) = ∑ k = 1 , a [ i ] > a [ k ] i − 1 f(k,j-1)=\sum_{k=1,a[i]>a[k]}^{i-1} f(k,j1)=k=1,a[i]>a[k]i1

所以答案即为 ∑ i = 1 n f ( i , m ) \sum_{i=1}^nf(i,m) i=1nf(i,m)

如果我们直接暴力枚举,将会有 O ( n 3 ) O(n^3) O(n3) 的时间复杂度,无法通过此题,那么,我们就需要树状数组来维护了。

我们可以先将数组离散化,然后再建 m m m 个树状数组,分别维护长度为 1 1 1 m m m 的严格上升子序列的方案数,用来优化时间复杂度,此时的时间复杂度为 O ( T n m log ⁡ n ) O(Tnm \log n) O(Tnmlogn)

看数据范围,有些变量还是需要开 long long 的,具体的实现看代码。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 100010
#define MOD 1000000007
using namespace std;
int n,m;
int lowbit(int x)
{
	return x&(-x);//求出lowbit,即最低位 
}
int T;
long long f[1010][1010],c[1010][1010];//开long long保险 
struct node{
	int a;
	int b;
}a[MAXN];
bool cmp(node x,node y)
{
	if(x.a!=y.a)
		return x.a<y.a;//排序,便于离散化 
	return x.b>y.b;
}
inline void put(int x,int y,long long z)
{
	//更新c数组 
	for(;x<=n;x+=lowbit(x))
		c[x][y]=(c[x][y]+z)%MOD;
	return;
}
long long get(int x,int y)
{
	//询问第x位之前长度为y的严格上升子序列的总数
	int S=0;
	for(;x;x-=lowbit(x))
		S=(c[x][y]+S)%MOD;//随时取模 
	return S;
}
int t;
inline void work()
{
	t++;
	memset(f,0,sizeof(f));
	memset(c,0,sizeof(c));//多组数据,别忘了随时清空 
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i].a),a[i].b=i;//离散化 
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			if(j==1)
				f[i][j]=1;
			以i结尾,长度为1的严格上升子序列就只有它本身,所以方案为1 
			else
				f[i][j]=get(a[i].b-1,j-1);
			//比a[i]小,也就是k,且长度为j-1,就符合转移条件 
			put(a[i].b,j,f[i][j]);
		}
	long long ans=0;
	for(int i=m;i<=n;i++)//小优化,m之前没有长为m的序列 
		ans=(ans+f[i][m])%MOD;//累加方案 
	printf("Case #%d: %lld\n",t,ans);	
}
int main()
{
	scanf("%d",&T);//T组数据 
	while(T--)
		work();
	return 0;
	//完结撒花~~ 
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值