dp 树状数组 逆序元组

wmq的队伍

发布时间: 2017年4月9日 17:06   最后更新: 2017年4月9日 17:07   时间限制: 2000ms   内存限制: 512M

交大上课需要打卡,于是在上课前的几分钟打卡机前往往会排起长队。

平时早睡早起早早打卡的wmq昨晚失眠,今天起晚了,于是他也加入了打卡队伍中。

这个时候,wmq发现了神奇的现象,打卡队伍可以按人们的身高看成一个队列,左边是队头,右边是队尾。

对于队列 a1...an ,wmq想知道其中存在多少的有序 k 元组 l1...lk

使得 1l1<l2<...<lkn ,并且有 al1>al2>...>alk

输入有多组数据

第一行是一个正整数 T 1T15 ,代表数据组数

每组数据第一行是两个正整数 nk 1n2104 1kmin(n,100)

n 代表队列的人数, k  的含义见题面

接下来一行有 n 个正整数,代表 1 n 的一个排列,表示队伍的身高情况

对于每组数据,输出一个整数,代表有序 k 元组的个数

考虑到数字可能很大,将答案对 109+7 取模之后输出

  复制
3
2 2
1 2
2 2
2 1
22 3
1 2 3 4 5 16 6 7 8 9 10 19 11 12 14 15 17 18 21 22 20 13
0
1
8

很容易想到用动态规划的方式来解决在这道题目,我们用dp[i][j][t]来表示在前i个队伍里,以t结尾的j元祖有多少个

这样的话转移就是dp[i][j][t] = sum(dp[i-1][j-1][m])其中m > t

但这样的话空间复杂度是4*10^10,我们发现dp[i]只与dp[i-1]有关,因此可以重复利用,所以 省掉一维只用dp[j][t]来表示就好了,这样的话复杂度降低到了2*10^6可以忍受了。

另外时间复杂度,因为i要从1循环到2*10^4 ,元组长度 要循环100次,比t大的m的循环最差也要循环2*10^4,因此总的时间复杂度为4*10^10显然不能通过。

我们要利用这道题目很重要的一点,队列中的人是1到n的一个排列,因此n最大不超过2*10^4,我们可以想到用树状数组的方法来求出sum(dp[i-1][j-1][m])的值,只需要logn的复杂度就行了,注意要为每个j元组都要开一个树状数组

int bitree[maxk][maxn];//为j元组开辟树状数组

这样的话sum(dp[i-1][j-1][m]) 的值实际上就等于sum(j-1,n) - sum(j-1,t);t是队伍中的第i个元素

注意爆int!别忘记取mod,比赛时候因为这两个低级错误WA了6发。。。。。55555

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define int long long
const int maxk = 105;
const int maxn = 20005;
int bitree[maxk][maxn];
int dp[maxk][maxn];//dp[i][j]表示 i元组,末尾为j的元组个数
const int MOD = 1e9 + 7; 
int n,k;
inline int lowbit(int x){
	return x&(-x);
}
void add(int tid,int pos,int val){
	while(pos <= n){
		bitree[tid][pos] += val;
		pos += lowbit(pos);
	}
}
int sum(int tid,int pos){
	int res = 0;
	while(pos > 0){
		res += bitree[tid][pos];
		pos -= lowbit(pos);
	}
	return res;
}
void init(){
	memset(bitree,0,sizeof(bitree));
	memset(dp,0,sizeof(dp));
}
main(){
	int T;scanf("%lld",&T);
	while(T--){
		init();
		scanf("%lld%lld",&n,&k);
		int now;
		for(int i = 1;i <= n;i++){
			scanf("%lld",&now);
			dp[1][now] = 1;
			add(1,now,1);
			for(int j = 2;j <= k;j++){
				dp[j][now] = (sum(j-1,n) - sum(j-1,now))% MOD;
				add(j,now,dp[j][now]);
			}
		}
		int ans = 0;
		for(int i = 1;i <= n;i++){
			ans  = (ans + dp[k][i]) % MOD;
		}
		printf("%lld\n",ans);
	}
	return 0;
} 




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值