hdu6348
序列计数
Time Limit: 4500/4000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 253 Accepted Submission(s): 75
Problem Description
度度熊了解到,1,2,…,n 的排列一共有 n!=n×(n−1)×⋯×1 个。现在度度熊从所有排列中等概率随机选出一个排列 p1,p2,…,pn,你需要对 k=1,2,3,…,n 分别求出长度为 k 的上升子序列个数,也就是计算满足 1≤a1 < a2 < … < ak ≤n 且 pa1 <pa2< … < pak 的 k 元组 (a1,a2,…,ak) 的个数。
由于结果可能很大,同时也是为了 ruin the legend, 你只需要输出结果对 1000000007(=109+7) 取模后的值。
Input
第一行包含一个整数 T,表示有 T 组测试数据。
接下来依次描述 T 组测试数据。对于每组测试数据:
第一行包含一个整数 n,表示排列的长度。
第二行包含 n 个整数 p1,p2, …, pn,表示排列的 n 个数。
保证 1≤T≤100,1≤n≤104,T 组测试数据的 n 之和 ≤105,p1,p2,…,pn 是 1,2,…,n 的一个排列。
除了样例,你可以认为给定的排列是从所有 1,2,…,n 的排列中等概率随机选出的。
Output
对于每组测试数据,输出一行信息 "Case #x: c1 c2 ... cn"(不含引号),其中 x 表示这是第 x 组测试数据,ci 表示长度为 i 的上升子序列个数对 1000000007(=109+7) 取模后的值,相邻的两个数中间用一个空格隔开,行末不要有多余空格。
Sample Input
2
4
1 2 3 4
4
1 3 2 4
Sample Output
Case #1: 4 6 4 1
Case #2: 4 5 2 0
题意:给定一个打乱的包含1~n数字的序列,分别求上升序列长度为1~n个序列个数。
思路:由长度小的求长度大的,如通过知道以x结尾的长度为2的上升序列个数可以知道在x后面且比x大的y的长度为3的上升序列个数(一部分,再将所有满足的x求和即可)。树状数组维护。由于输入的排列是随机选出的,一个有用的结论是最长上升子序列的长度期望是 O(sqrt(n))。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e4+10;
const ll mod = 1e9+7;
int a[maxn]; //每个位置的值
ll c[maxn], num[maxn]; //c为树状数组,num[i]为当前长度以i为结尾的序列数
int n;
int Lowbit(int x) // 2^k Lowbit(x)为x保留最右边1的结果
{
return x&(-x);
}
void update(int i, ll x)//i点增量为x ,更新树状数组
{
while(i <= n)
{
c[i] += x;
i += Lowbit(i);
}
}
ll sum(int x)//区间求和 [1,x]
{
int sum=0;
while(x>0)
{
sum=(sum + c[x])%mod;
x-=Lowbit(x);
}
return sum;
}
int main()
{
int t;
scanf("%d",&t);
for(int tt = 1;tt<=t;tt++)
{
scanf("%d",&n);
for(int i = 1;i<=n;i++)
{
scanf("%d",&a[i]);
num[i] = 1;
}
int len = n;
ll res = n;
printf("Case #%d:",tt);
while(res)
{
printf(" %lld",res);
len--;
memset(c,0,sizeof(c));
res = 0;
for(int i=1;i<=n;i++)
{
update(a[i],num[i]);
num[i] = sum(a[i]-1); //上升序列长度+1后以i为结尾的上升序列个数
res = (res + num[i])%mod;
}
}
while(len--)printf(" 0");
printf("\n");
}
}