序列计数
Time Limit: 4500/4000 MS (Java/Others)
Memory Limit: 262144/262144 K (Java/Others)
Problem Description
度度熊了解到,1,2,…,n 的排列一共有 n!=n×(n−1)×⋯×1 个。现在度度熊从所有排列中等概率随机选出一个排列
,你需要对 k=1,2,3,…,n 分别求出长度为 k 的上升子序列个数,也就是计算满足
且
的 k 元组
的个数。
由于结果可能很大,同时也是为了 ruin the legend, 你只需要输出结果对 取模后的值。
Input
第一行包含一个整数 T,表示有 T 组测试数据。
接下来依次描述 T 组测试数据。对于每组测试数据:
第一行包含一个整数 n,表示排列的长度。
第二行包含 n 个整数 p1,p2, …, pn,表示排列的 n 个数。
保证 ,T 组测试数据的 n 之和 ,p1,p2,…,pn 是 1,2,…,n 的一个排列。
除了样例,你可以认为给定的排列是从所有 1,2,…,n 的排列中等概率随机选出的。
Output
对于每组测试数据,输出一行信息 “Case #x: c1 c2 … cn”(不含引号),其中 x 表示这是第 x 组测试数据,ci 表示长度为 i 的上升子序列个数对
取模后的值,相邻的两个数中间用一个空格隔开,行末不要有多余空格。
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
题解:记
为长度为
的以
为结尾的上升子序列个数,那么有
如果按照
从小到大的顺序递推,求和式中
这一项条件自然满足,只需要对所有满足
的 k 求
的和,可以使用树状数组或者线段树维护,
由于输入的排列是随机选出的,一个有用的结论是最长上升子序列的长度期望是
#include<bits/stdc++.h>
using namespace std;
const int MAX=1e4+1;
const int MOD=1e9+7;
const double PI=acos(-1.0);
typedef long long ll;
inline int read()
{
int f=1,x=0;char ch;
do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
return f*x;
}
int A[MAX],a[MAX];
void add(int x,int n,int y){while(x<=n){A[x]+=y;if(A[x]>=MOD)A[x]-=MOD;x+=x&(-x);}}
int ask(int x){int ans=0;while(x){ans+=A[x];if(ans>=MOD)ans-=MOD;x-=x&(-x);}return ans;}
int d[2][MAX];//滚动数组d[i]表示以a[i]为结尾的上升子序列个数
int ans[MAX];
int main()
{
int T,cas=1;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)d[0][i]=1;
for(int i=1;i<=n;i++)A[i]=ans[i]=0;
ans[1]=n;
int now=1;
for(int i=2;i<=n;i++)//枚举上升子序列长度
{
if(ans[i-1]==0)break;
for(int j=1;j<=n;j++)
{
int y=ask(a[j]-1)%MOD;
(ans[i]+=y)%=MOD;
add(a[j],n,d[now^1][j]);
d[now][j]=y;
}
for(int j=1;j<=n;j++)A[j]=0;
now^=1;
}
printf("Case #%d:",cas++);
for(int i=1;i<=n;i++)printf(" %d",ans[i]);
puts("");
}
return 0;
}