「多校聯訓」I Love Random
阿新 • • 發佈:2021-10-20
可以說是一種dp的思維技巧
Problem
給定一個排列 \(p\),你可以智慧地按順序選擇多個區間使這個區間的每個數都等於這個區間的最小值。問最後能得到多少個不同的排列,答案對 \(10^9+7\) 取模。(\(len_p \le 5000\))
Solution
起初我想了一個區間 dp,想必也是大眾容易想到的。對於區間 \([l,r]\),找到其中最小值所在的位置 \(k\)。然後列舉 \(k\) 能覆蓋到的區間,字首和優化是 \(\mathcal {O}(n^2log_2n)\) 的。
但是這樣做會有一個很大的問題:
如圖,\(a_i>a_j>a_k\),當我們考慮 \(dp[i][k]\)
既然考慮原序列有後效性的話,考慮直接構造答案序列。
具體地,令 \(dp[i][j]\) 表示考慮獲得了長度為 \(i\) 的答案序列,第 \(i\) 個值為 \(p_j\) 的方案總數。
這樣的話,轉移就很簡單了(其實也不簡單)。
首先用一個通用套路,考慮 \([L_i,R_i]\) 為 \(p_i\) 能修改到的範圍。令最後的答案序列為 \(\{a\}\)。考慮兩種情況。令 \(a_{i-1}=k\)。令 \(a_i=t\)。
讀者容易證明這裡 \(a_i\) 是單調遞增的。換句話說,只要保證 \(\forall L[t] \le i \le R[t]\)
於是好像就做完了,\(L、R\) 陣列暴力就可以搞出來。
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <iostream> #define LL long long #define uint unsigned int using namespace std; const int MAXN = 5e3 + 5, Mod = 1e9 + 7; #define Debug(x) cerr << #x << ' ' << x #define hh cerr << endl int n, a[MAXN], L[MAXN], R[MAXN], dp[2][MAXN], res; int Qplus(int x, int y) { return x + y >= Mod ? x + y - Mod : x + y; } int main() { // freopen("C.in", "r", stdin); // freopen("C.out", "w", stdout); scanf("%d", &n); for(int i = 1; i <= n; i ++) scanf("%d", &a[i]); for(int i = 1; i <= n; i ++) { for(int j = i; j >= 1; j --) if(a[j] < a[i]) { L[i] = j + 1; break; } if(!L[i]) L[i] = 1; for(int j = i + 1; j <= n; j ++) if(a[j] < a[i]) { R[i] = j - 1; break; } if(!R[i]) R[i] = n; } for(int i = 1; i <= n; i ++) if(L[i] == 1) dp[1][i] = 1; for(int i = 2; i <= n; i ++) { bool f = (i & 1); int tmp = 0; for(int j = 1; j <= n; j ++) dp[f][j] = 0; // 滾動陣列清零( for(int j = 1; j <= n; j ++) { tmp = Qplus(tmp, dp[f ^ 1][j]); if(L[j] <= i && R[j] >= i) dp[f][j] = Qplus(dp[f][j], tmp); } } for(int i = 1; i <= n; i ++) res = Qplus(res, dp[n & 1][i]); printf("%d", res); return 0; }