\(\mathcal{Description}\)
給定排列 \(\{p_n\}\),可以在其上進行若干次操作,每次選取 \([l,r]\),把其中所有元素變為原區間最小值,求能夠得到的所有不同序列數量。答案對 \((10^9+7)\) 取模。
\(n\le5\times10^3\)。
\(\mathcal{Solution}\)
一類題型一起寫啦,再給出一道類似的題:
給定字符串 \(s\),\(s_i\in\{\text{'R'},\text{'G'},\text{'Y'}\}\),每次允許交換相鄰兩個元素。求最少交換次數,使得 \(s\) 中不存在兩個相鄰元素相等,或聲明無解。
\(n\le400\)。
不難看出它們都是 DP 題,但是若以從左到右的視角考慮問題,給定的操作具有強后效性,很難設計出一種靠譜的狀態。
這個時候可以奉行“拿來主義”(?),直接構造結果序列,將問題轉化為把原序列上某值拿到結果序列的某位置的最小操作次數 / 方案數,再根據實際情況設計算法就很方便了。
例如,對於求方案數的這題,令 \(f(i,j)\) 表示構造了結果序列的前 \(i\) 位,第 \(i\) 位用的是原序列的 \(p_j\) 的方案數。根據較大值不能覆蓋較小值設計轉移即可;對於最小化操作次數這題,令 \(f(i,r,g,k\in\{0,1,2\})\) 表示構造了結果序列前 \(i\) 位,用了 \(r\) 個 R
,\(g\) 個 G
,(\((i-r-g)\) 個 Y
,)最后一個位置顏色為 \(k\) 的最小代價。轉移利用類似冒泡排序的性質,在原序列里拿一個最近的顏色到當前位置即可。
\(\mathcal{Code}\)
只給那道計數題的代碼叭。
/*-Rainybunny-*/
#include <bits/stdc++.h>
#define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i )
const int MAXN = 5e3, MOD = 1e9 + 7;
int n, a[MAXN + 5], lef[MAXN + 5], rig[MAXN + 5], f[2][MAXN + 5];
inline void addeq( int& u, const int v ) { ( u += v ) >= MOD && ( u -= MOD ); }
inline void init() {
static int stk[MAXN + 5]; int top = 0;
rep ( i, 1, n ) {
while ( top && a[stk[top]] >= a[i] ) --top;
lef[i] = stk[top] + 1, stk[++top] = i;
}
stk[top = 0] = n + 1;
per ( i, n, 1 ) {
while ( top && a[stk[top]] >= a[i] ) --top;
rig[i] = stk[top] - 1, stk[++top] = i;
}
}
int main() {
freopen( "C.in", "r", stdin );
freopen( "C.out", "w", stdout );
scanf( "%d", &n );
rep ( i, 1, n ) scanf( "%d", &a[i] );
init();
rep ( i, 1, n ) if ( lef[i] == 1 ) f[1][i] = 1;
for ( int sta = 1, i = 2; i <= n; sta ^= 1, ++i ) {
rep ( j, 1, n ) f[!sta][j] = rig[j] >= i ? f[sta][j] : 0;
int s = 0;
rep ( j, 1, n ) {
if ( lef[j] <= i && i <= rig[j] ) addeq( f[!sta][j], s );
addeq( s, f[sta][j] );
}
}
int ans = 0;
rep ( i, 1, n ) addeq( ans, f[n & 1][i] );
printf( "%d\n", ans );
return 0;
}