CEOI2015 Day1Task2 Calvinball championship

同JZOJ.4806

题目大意

给定n个人,我们可以给这n个人分任意数量的队伍,相同队伍的人用相同数字来表示。如果有多种表示,我们认为字典序最小的表示才是有效的。
现在给定n和一个长度为n的序列,要求输出这个序列是所有可能的分队方式里面字典序第几小的。

Data Constraint
n10000

题解

可以注意到,一个序列中如果要出现k这个数,那么在他之前的位置1~k-1必定都出现过,这样构造出的序列才是合法的。
假如我们已经得到了前面m个元素,剩下i个元素待确定,前面的最大值是j,那么剩下的方案数显然只与i和j有关。
f[i][j] 表示当前待确定长度为i,前面的最大值是j。
有了 f 以后,我们就可以考虑每一个位置单独计算方案数。
具体来说,我们从后往前考虑每一个位置假设它前面与原序列完全符合的情况下,有多少序列字典序比原序列小,显然方案数是 (j1)f[i][j]
接下来的问题是如何计算 f
计算方案数的式子长度为i,写出来类似 1jjj...1(j+1)(j+1)...1(j+2)(j+2)...
每个1从这里开始第一次出现新的数k,所以方案数为1,然后1后面的连续一段可以选择1~k的数,所以方案数是k。
考虑f[i]如何由f[i-1]推来。当前这一位的后一位放k那么当前这位就有k种放法;后一位放k+1,就只有一种放法。
所以我们可以枚举前面的的最大值k,然后 f[i][k]=kf[i1][k]+f[i1][k+1]

SRC

#include
#include
#include
#include
#include
using namespace std ;

#define N 10000 + 10
typedef long long ll ;
const int MO = 1000007 ;

int Maxv[N] , f[2][N] ;
int a[N] ;
int n , ans ;

int main() {
    scanf( "%d" , &n ) ;
    for (int i = 1 ; i <= n ; i ++ ) scanf( "%d" , &a[i] ) , f[0][i] = 1 ;
    for (int i = 2 ; i <= n ; i ++ ) Maxv[i] = max( Maxv[i-1] , a[i-1] ) ;
    int now = 0 ;
    for (int i = n ; i >= 1 ; i -- ) {
        int tmp = (ll)f[now][Maxv[i]] * (a[i] - 1) % MO ;
        ans = (ans + tmp) % MO ;
        for (int k = 0 ; k < i ; k ++ ) {
            f[!now][k] = ((ll)k * f[now][k] % MO + f[now][k+1]) % MO ;
        }
        now = !now ;
    }
    printf( "%d\n" , (ans + 1) % MO ) ;
    return 0 ;
}

以上.

你可能感兴趣的:(CEOI2015 Day1Task2 Calvinball championship)