1. 程式人生 > 其它 >「多校聯訓」I Love Random

「多校聯訓」I Love Random

可以說是一種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]\)

的轉移時列舉到 \(j\),我們用到的是 \(dp[i][j-1]\),但是這顯然沒有圖中這種情況。

既然考慮原序列有後效性的話,考慮直接構造答案序列
具體地,令 \(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;
}