1. 程式人生 > 實用技巧 >DP搬運工1 [來自yyy--mengbier的預設型dp]

DP搬運工1 [來自yyy--mengbier的預設型dp]

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\)

放在中間就需要考慮了,但是隻有在當前數兩邊放一個的時候才用乘以 \(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);
}