1. 程式人生 > 實用技巧 >2020CCPC秦皇島 H. Holy Sequence

2020CCPC秦皇島 H. Holy Sequence

考慮每個數字 \(x\) 第一次出現在位置 \(p\) 的貢獻, 那麼就要計算 \(x\) 前後的合法序列的數量了.

定義這兩個 \(dp\) 陣列:

  • \(pre[i][j]\): 當 \(n=i\) 時, \(p_n=j\) 的合法序列數.
  • \(suff[i][j]\): \(a_1=j\) (無視 \(a_0\) 的限制, 值域相應\(+j\)) 長度為 \(i\) 的合法序列數.

這兩個陣列的遞推還是好求的, 與第二類斯特林數很像.

然後我們考慮平方怎麼處理. 對於 \(x\) 出現了 \(k\) 次的序列, 我們相當於可重複地選兩次 \(x\). 也算個經典技巧.

現在列舉每個數字 \(i\)

第一次出現在位置 \(j\) 的貢獻, 字首的方案數是 \(pre[i-1][j-1]\), 字尾分四類考慮:

  1. 兩次都選擇位置 \(j\).
  2. 兩次選擇位置相同但不是 \(j\).
  3. 兩次中一次選擇 \(j\), 一次不選 \(j\).
  4. 兩次選擇位置不同且都不選擇 \(j\).

每次選擇後面有 \(0-2\)\(i\) 時, 相當於這個位置被佔用了, 可以直接去掉, 剩的位置方案即 \(suff[n-j-x][i]\).

程式碼

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inc(x, l, r) for(int x = l; x <= r; x++)

const int N = 3e3;

int t, n, m;
ll pre[N + 5][N + 5], suff[N + 5][N + 5];
// pre[pos][num]	suff[len][num]

int main () {
	cin >> t;
	inc(p, 1, t) {
		cin >> n >> m;
		
		pre[0][0] = 1;
		inc(i, 1, n) {
			inc(j, 1, i) {
				pre[i][j] = (pre[i - 1][j] * j + pre[i - 1][j - 1]) % m;
			}
		}
		
		inc(i, 1, n) suff[1][i] = 1;
		inc(i, 2, n) {
			inc(j, 1, n + 1 - i) {
				suff[i][j] = (suff[i - 1][j] * j + suff[i - 1][j + 1]) % m;
			}
		}
		
		vector<ll> res(n + 1);
		inc(i, 1, n) {	// num
			inc(j, i, n) {	//	pos
				ll tmp = suff[n - j + 1][i];			
				if(j < n) tmp = (tmp + 3 * (n - j) * suff[n - j][i]) % m;
				if(j < n - 1) tmp = (tmp + (n - j) * (n - j - 1) % m * suff[n - j - 1][i]) % m;
				res[i] = (res[i] + pre[j - 1][i - 1] * tmp) % m;
			}
		}
		
		cout << "Case #" << p << ":\n";
		inc(i, 1, n) {
			cout<< res[i] << " \n"[i == n];
		}
	}
}