Input
第一行,一个整数N表示参赛人数。
第二行,N个整数,表示询问的分队方式的序列。
Output
一行,一个整数表示这种方式会在第几天被采用。答案对1,000,007取模。
Sample Input
3
1 2 2
Sample Output
4
Data Constraint
对于100%的数据,N ≤ 10000 , 数据保证询问的数列是一个有效的序列。
Hint
比赛各天的分队情况如下:
第一天:1,1,1
第二天:1,1,2
第三天:1,2,1
第四天:1,2,2
第五天:1,2,3
The Solution
首先注意到一个简单的性质,
一个数 x 要出现在序列里,必须要
在它前面的位置出现 x-1。
可以注意到,一个序列中如果要出现 k 这个数,那么在他之前的
位置 1~k-1 必定都出现过,这样构造出的序列才是合法的。
举个例子 1,2,2,2….3….1,2,1,2….3
然后我们可以设f[i][j][0..1]表示前i个数,最大的数是j,
0表示 < j这个数,1表示 = 这个数。
普通转移很显然,f[i]只与f[i-1],f[i]只与f[i+1]有关,所以我们可以用滚动数组。
从后往前,考虑每一个位置,与原序列完全符合,或字典序比原序列小
那么方案数显然是
f[i+1][max(x,j)][z]=((ll)f[i+1][max(x,j)][z]+f[i][j][l])
z表示滚动0..1
计算方案数的式子长度为i,
写出来类似
1∗j∗j∗.....1∗(j+1)∗(j+1)....∗1∗(j+2)(j+2)
这条式子很明显
因为你放一个数设为k,那么前面就有1~k的那么多个位置,再放入小于k的数的方案数显然为上述式子。
考虑 f[i]如何由 f[i-1]推来。当前这一位的后一位放 k 那么当前这
位就有 k 种放法;后一位放 k+1,就只有一种放法。
可以枚举最大值k
时间复杂度:O(N^2)
CODE
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define N 10005
#define mo 1000007
using namespace std;
int n,a[N];
int f[2][N][2],ans=0;// qian i ge shu zui da de shu shi j
//0 < 1 =
typedef long long ll;
int main()
{
freopen("data.in","r",stdin);
scanf("%d",&n);
fo(i,1,n) scanf("%d",&a[i]);
f[0][1][1] = 1;
int p=0,q=1;//o u
int mx = 1;
fo(i,1,n)
{
q = 1 - p;
mx = max(mx,a[i]);
fo(j,1,i)
{
f[q][j][1] = 0;
int tmp = max(0,j-a[i]+1) , t = min(j,a[i]-1);
f[q][j][0] =(f[p][j][0] * (ll)tmp + (f[p][j][0]+f[p][j][1]) * (ll)t + f[p][j-1][0]) % mo;
if (j < a[i]) f[q][j][0] += f[p][j-1][1];
}
f[q][mx][1] = 1;
p = q;
}
fo(i,1,n) ans = ((ll)ans + f[p][i][1] + f[p][i][0])%mo;
printf("%d\n",ans);
return 0;
}