dp板子

niubuniu發表於2024-06-15

01揹包
f[x]表示裝x重量時最大價值,f初值0;
n物品數量,m最大重量。w表示容量,v時價值
for (int i = 1; i <= n; i++)//物品
{
for(int j=m;j>=w[i];j--){//容量
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
完全揹包
for(int i=0;i<=m;i++){//揹包容量
for(int j=1;j<=n;j++){//物品數量
if(i>=w[j]){
f[i]=max(f[i],f[i-w[j]]+v[j]);
}
}
}
01揹包空間最佳化
dp賦初值1e9 dp[x]表示x價值的物品,所需的揹包容量是多少
for(int i=1;i<=n;i++){//物品數量
for(int j=100000;j>=v[i];j--){//最大價值,其中100000是指v價值的總和
dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
if(dp[j]<=w[i]){
ans=max(ans,j);
}
}
}
最大最小欄位和
maxx=-1e9,minn=1e9;
s[100005],dp1[100005],dp2[100005];
for(int i=1;i<=n;i++){
cin>>s[i];//
dp1[i]=dp2[i]=s[i];
}
for(int i=1;i<=n;i++)
{
dp1[i]=max(dp1[i-1]+s[i],s[i]);
dp2[i]=min(dp2[i-1]+s[i],s[i]);
maxx=max(dp1[i],maxx);
minn=min(dp2[i],minn);
}
cout<<maxx<<minn;//maxx是最大欄位和,minn最小欄位和。
注意這裡maxx求的是包含負數的最大欄位和,如果有其他要求。透過修改maxx,minn的初始值來達到目的。
例如要求最大欄位和必須是非負數,可以把maxx=0;根據特殊情況來進行特殊討論。
以下是這道題程式碼
最大公共子序列
string s,t;
cin>>s>>t;
for(int i=1;i<=s.size();i++)
l[i]=s[i-1];
for(int i=1;i<=t.size();i++)
r[i]=t[i-1];
for(int i=0;i<=s.size();i++){
for(int j=0;j<=t.size();j++){
if(i0||j0){
lcs[i][j]=0;
}
else
if(l[i]==r[j])
lcs[i][j]=lcs[i-1][j-1]+1;
else
lcs[i][j]=max(lcs[i-1][j],lcs[i][j-1]);
}
}
cout<<lcs[s.size()][t.size()];


拓撲排序+dp

include<bits/stdc++.h>

using namespace std;

define int long long

int n,m;
vector vt[100005];//鄰接矩陣儲存
queue qt;
int ans=0;
int dt[100005],num[100005];//dt儲存入度數,num儲存最大數量,也就是狀態轉移方程
void bfs(){
for(int i=1;i<=n;i++){
if(dt[i]0)
{
qt.push(i);//把入度數=0儲存進去
//break;
}
}
while(qt.size()!=0){
int u=qt.front();
qt.pop();
for(int i=0;i<vt[u].size();i++){
num[vt[u][i]]=max(num[vt[u][i]],num[u]+1);
dt[vt[u][i]]--;
if(dt[vt[u][i]]
0)
qt.push(vt[u][i]);
}
}
}
void solve(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
dt[b]++;
vt[a].emplace_back(b);
}
bfs();
for(int i=1;i<=n;i++){
ans=max(ans,num[i]);
}
cout<<ans;
return ;
}
signed main()
{
ios::sync_with_stdio(false);
solve();
return 0;
}
二維dp

include<bits/stdc++.h>

using namespace std;

define int long long

const int mod=1e9+7;
int dp[1005][1005];//i表示行,j表示列。
void solve(){
int n,m;
cin>>n>>m;
char c[n+5][m+5];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>c[i][j];
}
}
dp[1][1]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) {
if (c[i][j] == '.') {
if (i - 1 >= 1 && i - 1 <= n && c[i - 1][j] != '#') {
dp[i][j] += (dp[i - 1][j]%mod);//dp[i][j]的路徑數等於dp[i-1][j]+dp[i][j-1]
dp[i][j]%=mod;
}
if (j - 1 >= 1 && j - 1 <= m && c[i][j - 1] != '#')
dp[i][j] += (dp[i ][j-1]%mod);
dp[i][j]%=mod;
}
}
}
cout<<dp[n][m];
return ;
}
signed main()
{
ios::sync_with_stdio(false);
solve();
return 0;
}
經典拋硬幣問題求機率dp,機率一般都是小推大


說實話我感覺有點像揹包了,dp方程有兩種情況,要麼是正面要麼是反面

include

include "algorithm"

include "cmath"

include "set"

include "map"

include "vector"

define int long long

define PII pair<int,int>

const int N=1e6;
using namespace std;
double dp[3005][3005];
void solve(){
int n;
cin>>n;
double s[n+5];
for(int i=1;i<=n;i++){
cin>>s[i];
}
dp[1][1]=s[1];
dp[1][0]=1-s[1];
for(int i=2;i<=n;i++){
for(int j=0;j<=i;j++){
if(j==0)
dp[i][j]=dp[i-1][j](1-s[i]);
else
{
dp[i][j]=dp[i-1][j-1]
s[i]+dp[i-1][j](1-s[i]);//dp[i-1][j-1]s[i]表示第i枚硬幣是正面,dp[i-1][[j]*(1-s[i])表示第i枚硬幣是反面。
}
}
}
double ans=0;
for(int i=n/2+1;i<=n;i++) {
ans += dp[n][i];
}
printf("%.10lf",ans);
}
signed main()
{
int t=1;
while(t--){
solve();
}
}
摸石頭


dp[i]中i代表石頭數為i時誰是必贏的

include

include "algorithm"

include "cmath"

include "set"

include "map"

include "vector"

define int long long

define PII pair<int,int>

const int N=1e6;
using namespace std;
int dp[N];
void solve(){
int n,k;
cin>>n>>k;
int s[n+5];
for(int i=1;i<=n;i++)
cin>>s[i];
for(int i=1;i<=k;i++){//代表石頭數
for(int j=1;j<=n;j++){
if(i-s[j]>=0){
if(dp[i-s[j]]0)
dp[i]=1;
}
}
}
if(dp[k]
1)
cout<<"First";
else
cout<<"Second";

}
signed main()
{
int t=1;
// cin>>t;
while(t--){
solve();
}
}
狀態機模型
1049. 大盜阿福
阿福是一名經驗豐富的大盜。趁著月黑風高,阿福打算今晚洗劫一條街上的店鋪。

這條街上一共有 N 家店鋪,每家店中都有一些現金。

阿福事先調查得知,只有當他同時洗劫了兩家相鄰的店鋪時,街上的報警系統才會啟動,然後警察就會蜂擁而至。

作為一向謹慎作案的大盜,阿福不願意冒著被警察追捕的風險行竊。

他想知道,在不驚動警察的情況下,他今晚最多可以得到多少現金?

輸入格式

輸入的第一行是一個整數 T,表示一共有 T 組資料。

接下來的每組資料,第一行是一個整數 N ,表示一共有 N 家店鋪。

第二行是 N 個被空格分開的正整數,表示每一家店鋪中的現金數量。

每家店鋪中的現金數量均不超過1000。

輸出格式

對於每組資料,輸出一行。

該行包含一個整數,表示阿福在不驚動警察的情況下可以得到的現金數量。

資料範圍

1≤T≤50
1≤N≤105

輸入樣例:

2
3
1 8 2
4
10 7 6 14
輸出樣例:

8
24
樣例解釋

對於第一組樣例,阿福選擇第2家店鋪行竊,獲得的現金數量為8。

對於第二組樣例,阿福選擇第1和4家店鋪行竊,獲得的現金數量為10+14=24。

include <bits/stdc++.h>

using namespace std;

define read(a) scanf("%d", &a);

const int N = 1e5 + 10, INF = 1e9;
int t, n;
int w[N], f[N][2];
int main() {
read(t);
while(t--) {
read(n);
for(int i = 1; i <= n; i++) read(w[i]);
f[0][0] = 0, f[0][1] = -INF;
for(int i = 1; i <= n; i++) {
f[i][0] = max(f[i - 1][0], f[i - 1][1]);
f[i][1] = f[i - 1][0] + w[i];
}
printf("%d\n", max(f[n][1], f[n][0]));
}
return 0;
}
狀態壓縮DP
1064. 小國王
在 n×n 的棋盤上放 k 個國王,國王可攻擊相鄰的 8 個格子,求使它們無法互相攻擊的方案總數。

輸入格式

共一行,包含兩個整數 n 和 k。

輸出格式

共一行,表示方案總數,若不能夠放置則輸出0。

資料範圍

1≤n≤10,
0≤k≤n2

輸入樣例:

3 2
輸出樣例:

16

include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 12, M = 1 << 10, K = 110;
int n, m;
vector state;
int cnt[M];
vector head[M];
LL f[N][K][M];
bool check(int state) {
for (int i = 0; i < n; i++)
if ((state >> i & 1) && (state >> i + 1 & 1))
return false;
return true;
}
int count(int state) {
int res = 0;
for (int i = 0; i < n; i++) res += state >> i & 1;
return res;
}
int main() {
cin >> n >> m;
for (int i = 0; i < 1 << n; i++)
if (check(i)) {
state.push_back(i);
cnt[i] = count(i);
}
for (int i = 0; i < state.size(); i++)
for (int j = 0; j < state.size(); j++) {
int a = state[i], b = state[j];
if ((a & b) == 0 && check(a | b))
head[i].push_back(j);
}
f[0][0][0] = 1;
for (int i = 1; i <= n + 1; i++)
for (int j = 0; j <= m; j++)
for (int a = 0; a < state.size(); a++)
for (int b : head[a]) {
int c = cnt[state[a]];
if (j >= c)
f[i][j][a] += f[i - 1][j - c][b];
}
cout << f[n + 1][m][0] << endl;
return 0;
}
區間DP
1068. 環形石子合併
將 n 堆石子繞圓形操場排放,現要將石子有序地合併成一堆。

規定每次只能選相鄰的兩堆合併成新的一堆,並將新的一堆的石子數記做該次合併的得分。

請編寫一個程式,讀入堆數 n 及每堆的石子數,並進行如下計算:

選擇一種合併石子的方案,使得做 n−1 次合併得分總和最大。
選擇一種合併石子的方案,使得做 n−1 次合併得分總和最小。
輸入格式

第一行包含整數 n,表示共有 n 堆石子。

第二行包含 n 個整數,分別表示每堆石子的數量。

輸出格式

輸出共兩行:

第一行為合併得分總和最小值,

第二行為合併得分總和最大值。

資料範圍

1≤n≤200

輸入樣例:

4
4 5 9 4
輸出樣例:

43
54

include <bits/stdc++.h>

using namespace std;
const int N = 410, INF = 0x3f3f3f3f;
int n;
int w[N], s[N];
int f[N][N], g[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> w[i];
w[i + n] = w[i];
}
for (int i = 1; i <= n * 2; i++)
s[i] = s[i - 1] + w[i];
memset(f, 0x3f, sizeof f);
memset(g, -0x3f, sizeof g);
for (int len = 1; len <= n; len++)
for (int l = 1; l + len - 1 <= n * 2; l++) {
int r = l + len - 1;
if (l == r)
f[l][r] = g[l][r] = 0;
else {
for (int k = l; k < r; k++) {
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
}
}
}
int minv = INF, maxv = -INF;
for (int i = 1; i <= n; i++) {
minv = min(minv, f[i][i + n - 1]);
maxv = max(maxv, g[i][i + n - 1]);
}
cout << minv << endl << maxv << endl;
return 0;
}
樹形DP
1072. 樹的最長路徑
給定一棵樹,樹中包含 n 個結點(編號1~n)和 n−1 條無向邊,每條邊都有一個權值。

現在請你找到樹中的一條最長路徑。

換句話說,要找到一條路徑,使得使得路徑兩端的點的距離最遠。

注意:路徑中可以只包含一個點。

輸入格式

第一行包含整數 n。

接下來 n−1 行,每行包含三個整數 ai,bi,ci,表示點 ai 和 bi 之間存在一條權值為 ci 的邊。

輸出格式

輸出一個整數,表示樹的最長路徑的長度。

資料範圍

1≤n≤10000
1≤ai,bi≤n,
−105≤ci≤10^{5}

輸入樣例:

6
5 1 6
1 4 5
6 3 9
2 6 8
6 1 7
輸出樣例:

22

include <bits/stdc++.h>

using namespace std;
const int N = 10010, M = N * 2;
int n;
int h[N], e[M], w[M], ne[M], idx;
int ans;
void add(int a, int b, int c) {
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
int dfs(int u, int father) {
int dist = 0;//表示從當前點往下走的最大長度
int d1 = 0, d2 = 0;
for(int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if(j == father) continue;
int d = dfs(j, u) + w[i];
dist = max(dist, d);
if(d >= d1) d2 = d1, d1 = d;
else if(d > d2) d2 = d;
}
ans = max(ans, d1 + d2);
return dist;
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
for(int i = 0; i < n - 1; i++) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
dfs(1, -1);
cout << ans << endl;
return 0;
}
數位DP
1081. 度的數量
求給定區間 [X,Y] 中滿足下列條件的整數個數:這個數恰好等於 K個互不相等的 B 的整數次冪之和。

例如,設 X=15,Y=20,K=2,B=2,則有且僅有下列三個數滿足題意:

17=24+20
18=24+21
20=24+22

輸入格式

第一行包含兩個整數 X 和 Y,接下來兩行包含整數 K 和 B。

輸出格式

只包含一個整數,表示滿足條件的數的個數。

資料範圍

1≤X≤Y≤231−1,
1≤K≤20,
2≤B≤10

輸入樣例:

15 20
2
2
輸出樣例:

3

include <bits/stdc++.h>

using namespace std;
const int N = 40;
int K, B;
int f[N][N];
void init() {
for(int i = 0; i < N; i++) {
for(int j = 0; j <= i; j++)
if(!j) f[i][j] = 1;
else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}
}
int dp(int n) {
if(!n) return 0;
vector nums;
while(n) {
nums.push_back(n % B);
n /= B;
}
int res = 0, last = 0;
for(int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
if(x) {
res += f[i][K - last];
if(x > 1) {
if(K - last - 1 >= 0) res += f[i][K - last - 1];
break;
}
else {
last++;
if(last > K) break;
}
}
if(!i && last == K) res++;
}
return res;
}
int main() {
init();
int l, r;
cin >> l >> r >> K >> B;
cout << dp(r) - dp(l - 1) << endl;
}
單調佇列最佳化DP
135. 最大子序和
輸入一個長度為 n 的整數序列,從中找出一段長度不超過 m 的連續子序列,使得子序列中所有數的和最大。

注意: 子序列的長度至少是 11。

輸入格式

第一行輸入兩個整數 n,m。

第二行輸入 n 個數,代表長度為 n 的整數序列。

同一行數之間用空格隔開。

輸出格式

輸出一個整數,代表該序列的最大子序和。

資料範圍

1≤n,m≤300000,
保證所有輸入和最終結果都在 int 範圍內。

輸入樣例:

6 4
1 -3 5 1 -2 3
輸出樣例:

7

include <bits/stdc++.h>

using namespace std;
const int N = 300010, INF = 1e9;
int n, m;
int s[N];
int q[N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &s[i]), s[i] += s[i - 1];
int res = -INF;
int hh = 0, tt = 0;
for (int i = 1; i <= n; i++) {
if (q[hh] < i - m) hh++;
res = max(res, s[i] - s[q[hh]]);
while (hh <= tt && s[q[tt]] >= s[i]) tt--;
q[++tt] = i;
}
printf("%d\n", res);
return 0;
}