DP搬運工1 [來自yyy--mengbier的預設型dp]
阿新 • • 發佈:2020-08-20
DP搬運工1
題目描述
給你 \(n,K\) ,求有多少個 \(1\) 到 \(n\) 的排列,滿足相鄰兩個數的 \(max\) 的和不超過 \(K\)。
輸入格式
一行兩個整數 \(n,K\)。
輸出格式
一行一個整數 \(ans\) 表示答案 \(mod\ 998244353\)。
樣例
樣例輸入 1
4 10
樣例輸出 1
16
樣例輸入 2
10 66
樣例輸出 2
1983744
資料範圍與提示
有 \(50\) 個測試點,第 \(i\) 個測試點為 \(n=i\),\(K \leqslant n^2\) 。
分析
用學長的題解來說這個叫做預設性 \(dp\) (其實也不知道啥意思)
這個題我們考慮往裡邊插入數,因為每一次要取 \(max\) ,所以我們根據當前插入的值兩邊還可不可以放數來進行轉移。
如果可以放入一個數,那麼當前這個數之對和貢獻一次。
如果兩邊可以放入兩個數,那麼這個數是沒有貢獻的。
如果兩邊都不放數,那麼它貢獻兩次。
所以我們定義 \(f[i][j][k]\) 為放到第 \(i\) 個數,可以放的位置有 \(j\) 個。和為 \(k\)。
因為可以放在序列中,也可以放在兩端,所以我們分開來考慮。
放在兩端的時候就沒有兩邊放兩個數的情況了,但是兩端有兩種情況,所以加的時侯 \(f[i-1][j][k]\)
放在中間就需要考慮了,但是隻有在當前數兩邊放一個的時候才用乘以 \(2\) ,所以我們就可以愉快的轉移了。
程式碼
#include<cstdio> #include<algorithm> #include<iostream> #include<cstring> #include<cmath> using namespace std; //以下好多行是卡常 const int L=1<<20; char buffer[L],*S,*T; #define lowbit(x) (x & -x) #define getchar() (S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T)?EOF:*S++) #define inline __inline__ __attribute__((__always_inline__)) #define max(a,b) (a>b?a:b) #define re register const int maxn = 52; const int mod = 998244353; int f[maxn][maxn][maxn*maxn]; int n; inline int read(){ int s = 0,f = 1; char ch = getchar(); while(!isdigit(ch)){ if(ch == '-')f = -1; ch = getchar(); } while(isdigit(ch)){ s = s * 10 + ch - '0'; ch = getchar(); } return s * f; } int main(){ n = read(); int K = read(); f[1][0][0] = 1; for(int i = 2;i <= n;++i){ int jl1 = min(i,n-i)+1; //找到當前最多有多少位置能放 int jl2 = min(K,i*i);//找到當前最大的和 for(int j = 0;j <= jl1; ++j){ for(int k = 0;k <= jl2; ++k){ if(!f[i-1][j][k])continue; int jl = f[i-1][j][k] * 2 % mod;//第一種放在兩端的情況 f[i][j+1][k] = (f[i][j+1][k] + jl) % mod; f[i][j][k+i] = (f[i][j][k+i] + jl) % mod; if(!j)continue; jl = f[i-1][j][k] * 1ll * j % mod;//以下是放在序列中間的情況 f[i][j+1][k] = (f[i][j+1][k] + jl) % mod; f[i][j][k+i] = (f[i][j][k+i] + jl * 2ll % mod) % mod; f[i][j-1][k+2*i] = (f[i][j-1][k+2*i] + jl) % mod; } } } int ans = 0; for(int i = 0;i <= K;++i){//把小於等於 K 的所有情況都加起來 ans = (ans + f[n][0][i]) % mod; } printf("%d\n",ans); }