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,K\leq 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\n" , ans % md) ;
return 0 ;
}