JZOJ 6809. 【2020.10.29提高组模拟】不难题
题目大意
有
K
K
K 个
1
−
N
1-N
1 − N 的排列,每次可以挑选一个队列取出队首,但不能连续取出
K
K
K 个相同的数,要求取出每个区间
[
l
,
r
]
[l,r]
[ l , r ] 中排列且不能连续取出
r
−
l
+
1
r-l+1
r − l + 1 个相同的数的方案数。
N
,
K
≤
300
N,Kleq 300
N , K ≤ 3 0 0
题解
这题可以联想到平面上只能向右向上走,要求到达某个点且有若干个点不能经过的方案数, 可以用容斥来做设
f
i
f_i
f i 表示仅仅经过了第
i
i
i 个不能经过的点的方案数,用总方案数减去其他
f
j
f_j
f j 即为
f
i
f_i
f i ,简单组合数计算即可。 这题也是同理,但维度变成了
K
K
K 维,同样设
f
i
f_i
f i 表示仅有
i
i
i 连续出现了若干次,那么可以枚举
f
j
f_j
f j 转移到
f
i
f_i
f i ,当然要注意这里不只是组合数,而是先要到达每个
i
i
i 前一个的位置,然后再乘阶乘,暴力做是
O
(
N
2
K
3
)
O(N^2K^3)
O ( N 2 K 3 ) 的,中间计算简单优化一下可以达到
O
(
N
2
K
2
)
O(N^2K^2)
O ( N 2 K 2 ) ,仍旧是过不了。 发现题目规定了是随机的,也就是区间跨度越大,可以转移的量就会越少,那么直接暴力把每次可以转移的记录下来,就可以过了。 注意区间右端点最好要从左端点
+
1
+1
+ 1 开始枚举,不然常数大可能过不了,另外可以尽量减少模运算的次数。
代码
#include <cstdio>
#include <queue>
using namespace std;
#define N 310
#define ll long long
#define md 1000000007
int a[ N] [ N] , p[ N] [ N] ;
ll F[ N * N] , G[ N * N] , f[ N] ;
int Sum[ N] [ N] , S[ N] [ N] , sum[ N] , s[ N] ;
queue< int > q[ N] ;
ll C ( int x, int y) {
return F[ x] * G[ y] % md * G[ x - y] % md;
}
ll ksm ( ll x, ll y) {
if ( ! y) return 1 ;
ll l = ksm ( x, y / 2 ) ;
if ( y % 2 ) return l * l % md * x % md;
return l * l % md;
}
int main ( ) {
int n, m, i, j, k, l, h, x;
scanf ( "%d%d" , & m, & n) ;
F[ 0 ] = 1 ;
for ( i = 1 ; i < N * N; i++ ) F[ i] = F[ i - 1 ] * i % md;
G[ N * N - 1 ] = ksm ( F[ N * N - 1 ] , md - 2 ) ;
for ( i = N * N - 2 ; i >= 0 ; i-- ) G[ i] = G[ i + 1 ] * ( i + 1 ) % md;
for ( i = 1 ; i <= m; i++ ) {
for ( j = 1 ; j <= n; j++ ) {
scanf ( "%d" , & a[ i] [ j] ) ;
p[ i] [ a[ i] [ j] ] = j;
}
a[ i] [ n + 1 ] = p[ i] [ n + 1 ] = n + 1 ;
}
ll ans = 0 , tot;
for ( i = 1 ; i < m; i++ ) {
for ( k = 1 ; k <= n + 1 ; k++ ) sum[ k] = p[ i] [ k] - 1 , s[ k] = 1 ;
for ( k = 1 ; k <= n + 1 ; k++ )
for ( l = 1 ; l < k; l++ ) if ( p[ i + 1 ] [ a[ i] [ l] ] < p[ i + 1 ] [ a[ i] [ k] ] ) q[ a[ i] [ k] ] . push ( a[ i] [ l] ) , Sum[ a[ i] [ k] ] [ a[ i] [ l] ] = k - l - 1 , S[ a[ i] [ k] ] [ a[ i] [ l] ] = 1 ;
for ( j = i + 1 ; j <= m; j++ ) {
for ( h = 1 ; h <= n + 1 ; h++ ) {
k = a[ i] [ h] ;
s[ k] = s[ k] * C ( sum[ k] + p[ j] [ k] - 1 , sum[ k] ) % md;
sum[ k] + = p[ j] [ k] - 1 ;
f[ k] = s[ k] * F[ j - i + 1 ] % md;
x = q[ k] . size ( ) , tot = 0 ;
while ( x-- ) {
l = q[ k] . front ( ) ;
q[ k] . pop ( ) ;
S[ k] [ l] = S[ k] [ l] * C ( Sum[ k] [ l] + p[ j] [ k] - p[ j] [ l] - 1 , Sum[ k] [ l] ) % md;
Sum[ k] [ l] + = p[ j] [ k] - p[ j] [ l] - 1 ;
tot + = f[ l] * S[ k] [ l] % md;
if ( p[ j + 1 ] [ l] < p[ j + 1 ] [ k] ) q[ k] . push ( l) ;
}
f[ k] = ( f[ k] - tot % md * F[ j - i + 1 ] % md + md) % md;
}
ans + = f[ n + 1 ] * G[ j - i + 1 ] % md;
}
}
printf ( "%lld
" , ans % md) ;
return 0 ;
}