題面:求出滿足以下條件的 n*m 的 01 矩陣個數:

    (1)第 i 行第 1~li 列恰好有 1 個 1 (li+1到ri-1不能放1)

    (2)第 i 行第 ri~m 列恰好有 1 個 1。

    (3)每列至多有 1 個 1

    對於 20%的資料,n,m<=12。

    對於 40%的資料,n,m<=50。

    對於 70%的資料,n,m<=300。

    對於 100%的資料,n,m<=3000,1<=li<ri<=m。

(考試前一天晚上玩過頭了,整場考試直接划水,本題打了個狀壓,想不出來怎麼優化就跳了)

 通過對題目的初步分析 本題是一類計數問題

  那麼考慮這類問題解法通常為應用計數類DP或數學模型求解(本題顯然DP色彩很濃厚)

然而 無論應用實際意義,數學模型都無法推出完整的式子 那麼我們就需要思考DP類做法(事實上DP也是更為通用的做法)

  常規思考 問題狀態有行,列,點 很容易想出狀壓DP的做法f[i][j]代表進行到第i行同時第i行狀態為j

然而根據資料範圍狀壓只能拿到20pts,顯然不是正解 問題在於對於較大的m無法記錄其狀態 這樣 直接否定了對行進行狀壓的過程

  思考問題在於列舉同一行點的狀態的時間空間複雜度無法承受 那麼為我們就要儘量降低這種複雜度

一種很顯然的想法是既然我們不能記錄同一行點的狀態 那麼縮小問題 記錄單個點的狀態的代價顯然是能接受的

  那麼思路逐漸清晰 我們需要考慮問題中行或列與節點的關係

一種常規DP設計手法是根據目標設計狀態,階段與決策 那麼應用到本題上 目標是求n*m矩陣在一定條件下合法放置1的不同矩陣數

倒推分解問題 得 問題實際上是行或列放置1方案數的轉移

  因此 設計狀態f[i][j]表示已經進行到i列 其中有j列中的1放置在右區間,那麼我們最終的目標就是f[m][n];

(這道題DP設計很新穎,思維量較高,然而其本質仍然是計數類DP的設計理念:通過精確劃分使得狀態各決策間滿足加法原理 子狀態間滿足乘法原理(不重不漏))

顯然本題狀態劃分是基於左右區間的互斥性 也就是說通過分別計算當前狀態左右區間合法方案數來推出當前狀態合法方案數

  很容易想到對於右區間 f[i][j] = f[i - 1][j] + f[i - 1] * (r[i] - (j - 1))  <1>  //其中r[i]代表右區間在i列及其以右的區間總數 l[i]同理

而有趣的是本題左右區間計算並不是完全獨立的 相反 左區間的計算正是基於右區間已知的情況下進行的 (這為我們進行DP設計,具體到狀態轉移提供了新思路)

公式並不難推 f[i][j] = A(i - j - l[i - 1],l[i] - l[i - 1])  <2> ;最終根據乘法原理 狀態f[i][j] = <1> * <2>;

 1 #include<bits/stdc++.h>
2 using namespace std;
3 #define I int
4 #define LL long long
5 #define C char
6 #define RE register
7 #define L inline
8 const I mod = 998244353;
9 const I MAXN = 3050;
10 I n,m,l[MAXN],r[MAXN],j[MAXN],y[MAXN],f[MAXN][MAXN];
11 L I read(); L LL qpow(I,I); L LL A(I,I); L I getmin(I,I);
12 signed main(){
13 n = read(); m = read();j[0] = 1;f[0][0] = 1;
14 for(RE I i(1);i <= n; ++ i) ++l[read()],++r[read()];
15 for(RE I i(1);i <= m; ++ i) l[i] += l[i - 1],r[i] += r[i - 1];
16 for(RE I i(1);i <= m; ++ i) j[i] = 1ll * j[i - 1] * i % mod;
17 y[m] = qpow(j[m],mod - 2);
18 for(RE I i(m); i ; -- i) y[i - 1] = 1ll * y[i] * i % mod;
19 for(RE I i(1);i <= m; ++ i){
20 f[i][0] = 1ll * f[i - 1][0] * A(i - l[i - 1],l[i] - l[i - 1]) % mod;
21 for(RE I j(1);j <= getmin(n,i); ++ j)
22 f[i][j] = (f[i - 1][j] + 1ll * f[i - 1][j - 1] * (r[i] - j + 1)%mod) * A(i - j - l[i - 1],l[i] - l[i - 1]) % mod;
23 }printf("%d\n",f[m][n]);
24 }
25 L I read(){ RE I x(0); RE C z = getchar(); while(!isdigit(z)) z = getchar(); while(isdigit(z)) x = (x << 3) + (x << 1) + (z ^ 48),z = getchar(); return x; }
26 L LL qpow(I a,I b){
27 LL res(1),base(a);
28 while(b){
29 if(b & 1) res = 1ll * res * base % mod;
30 base = 1ll * base * base % mod;
31 b >>= 1;
32 }return res; }
33 L LL A(I n,I m){ if(m > n) return 0; return 1ll * j[n] * y[n - m] % mod; }
34 L I getmin(I a,I b){ return a < b ? a : b; }

總的來說本題真的是DP中一道精彩的問題 對於DP狀態設計,決策都提供了較為新穎的思路 建議反覆咀嚼