【Acwing提高】DP·數字三角形模型
技術標籤:acwing
DP·數字三角形模型
知識點
題目 | 擴充套件方式 |
---|---|
摘花生 | 三角形擴充套件為矩陣 |
最低通行費 | 不能走回頭路,步數(時間限制) |
方格取數 | 走兩次,走過的變為0 |
傳紙條 | 雙向傳,傳過的不能再傳 |
題目
A:摘花生
思路
狀態表示:
F
[
i
]
[
j
]
F[i][j]
F[i][j]
集合:所有從(1,1)走到(i,j)的所有路線
屬性:Max
狀態計算:集合的劃分
為什麼劃分:整個集合不好求,分而治之求劃分的最大值
最後一步從左邊過來,最後一步從上面過來
DP分析 | 公式 |
---|---|
方程 | f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) + w [ i ] [ j ] f[i][j]=max(f[i-1][j],f[i][j-1])+w[i][j] f[i][j]=max(f[i−1][j],f[i][j−1])+w[i][j] |
邊界,初值 | 初值為0 |
目標 | f [ R ] [ C ] f[R][C] f[R][C] |
程式碼
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n,m;
const int N=105;
ll w[N][N],dp[N][N];
int main()
{
cin>>t;
while(t--)
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>w[i][j];
for(int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+w[i][j];
cout<<dp[n][m]<<endl;
}
return 0;
}
B:最低通行費
思路
2n-1可以推出不走回頭路,實際上就是摘花生
並非所有的都可以從左上和右上兩個過來
需要特判
程式碼
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n;
const int N=105,INF=0x3f3f3f3f;
ll w[N][N],dp[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>w[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==1&&j==1)dp[i][j]=w[i][j];//特判
else if(i==1)dp[i][j]=dp[i][j-1]+w[i][j];//特判
else if(j==1)dp[i][j]=dp[i-1][j]+w[i][j];//特判
else dp[i][j]=min(dp[i-1][j],dp[i][j-1])+w[i][j];
}
cout<<dp[n][n]<<endl;
return 0;
}
C:方格取數
思路
走兩次,走過的會變為0(不是不能重複經過!)
錯誤的思路
走兩次,第一次最優,然後標記已經走過的,然後第二次老樣子再走一遍不能經過已經標記的,兩次合起來得到答案
錯誤點:第一次選取最優路徑後第二次未必是最優路徑,不能又區域性最優推出全域性最優
正解
思考兩個路徑同時走:
狀態表示:
f
[
i
1
]
[
j
1
]
[
i
2
]
[
j
2
]
f[i_1][j_1][i_2][j_2]
f[i1][j1][i2][j2]表示所有從(1,1)分別走到(i1,j1)(i2,j2)的路徑的最大值
優化:
f
[
k
]
[
i
1
]
[
i
2
]
f[k][i_1][i_2]
f[k][i1][i2]表示所有從(1,1)分別走到(i1,k-i1)(i2,k-i2)的路徑的最大值
狀態劃分,k表示橫縱座標之和
因為我們是同時走的,所以我們走過的步數是相同的所以只需總步數和一個軸的左邊就可以推出另外一個軸的座標了
j
1
=
k
−
i
1
j_1=k-i_1
j1=k−i1那麼我們就可以實現降維優化
狀態轉移以第一個為例
不重合
f
[
k
]
[
i
1
]
[
i
2
]
=
f
[
k
−
1
]
[
i
1
−
1
]
[
i
2
−
1
]
+
w
[
i
1
]
[
k
−
i
1
]
+
w
[
i
2
]
[
k
−
i
2
]
f[k][i_1][i_2]=f[k-1][i_1-1][i_2-1]+w[i_1][k-i_1]+w[i_2][k-i_2]
f[k][i1][i2]=f[k−1][i1−1][i2−1]+w[i1][k−i1]+w[i2][k−i2]
重合
i
1
=
i
2
i_1=i_2
i1=i2
f
[
k
]
[
i
1
]
[
i
2
]
=
f
[
k
−
1
]
[
i
1
−
1
]
[
i
2
−
1
]
+
w
[
i
1
]
[
k
−
i
1
]
f[k][i_1][i_2]=f[k-1][i_1-1][i_2-1]+w[i_1][k-i_1]
f[k][i1][i2]=f[k−1][i1−1][i2−1]+w[i1][k−i1]
程式碼
#include<bits/stdc++.h>
using namespace std;
int w[15][15];
int f[40][40][40];
int n,a,b,c;
int main()
{
cin>>n;
while(cin>>a>>b>>c,a||b||c)w[a][b]=c;//多個數等於0的時候可以這樣簡化
for(int k=2;k<=2*n;k++)
{
for(int i1=1;i1<=n;i1++)
{
for(int i2=1;i2<=n;i2++)
{
int j1=k-i1,j2=k-i2;
if(j1>=1&&j1<=n&&j2>=1&&j2<=n)
{
int t=w[i1][j1];
if(i1!=i2)t+=w[i2][j2];//取走之和就變成0了,沒影響嘛
int &x=f[k][i1][i2];//利用應用減小程式碼量
x =max(x,f[k-1][i1-1][i2-1]+t);
x =max(x,f[k-1][i1][i2-1]+t);
x =max(x,f[k-1][i1-1][i2]+t);
x =max(x,f[k-1][i1][i2]+t);
}
}
}
}
cout<<f[2*n][n][n];
return 0;
}
D:傳紙條
思路
實際上和方格取數是一樣的,兩邊互傳可以轉化為從左上到右下傳兩次。題目區別是一個是傳過的變為0還可以再傳,一個是不可以再經過。
但是因為資料都是非負的,所以直接設定為0的話可以肯定走重複的一定比繞開得分要低,這個是一個技巧
證明看這個:連結
程式碼
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int w[N][N];
int f[N][N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>w[i][j];
for(int k=2;k<=n+m;k++)
{
for(int i1=1;i1<k;i1++)//這邊用<k的話下面就不用寫if了
{
for(int i2=1;i2<k;i2++)
{
int j1=k-i1,j2=k-i2;
int t=w[i1][j1];
if(i1!=i2)t+=w[i2][j2];
int &x=f[k][i1][i2];
x=max(x,f[k-1][i1-1][i2-1]+t);
x=max(x,f[k-1][i1][i2-1]+t);
x=max(x,f[k-1][i1-1][i2]+t);
x=max(x,f[k-1][i1][i2]+t);
}
}
}
cout<<f[n+m][n][n];//注意這裡是nn不是nm
return 0;
}
另外一種思考方式
主要是針對處理不可以重複走的:只有不重合的才去更新其他狀態,否則不更新
程式碼
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int w[N][N];
int f[N][N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>w[i][j];
for(int k=2;k<=n+m;k++)
{
for(int i1=1;i1<k;i1++)
{
for(int i2=1;i2<k;i2++)
{
int j1=k-i1,j2=k-i2;
int t=w[i1][j1];
if(i1!=i2||k==2||k==n+m)//只有不重合的才去更新其他狀態,否則不更新
{
t+=w[i2][j2];
int &x=f[k][i1][i2];
x=max(x,f[k-1][i1-1][i2-1]+t);
x=max(x,f[k-1][i1][i2-1]+t);
x=max(x,f[k-1][i1-1][i2]+t);
x=max(x,f[k-1][i1][i2]+t);
}
}
}
}
cout<<f[n+m][n][n];
return 0;
}
總結
如何簡化程式碼
int &x=f[k][i1][i2];
while(cin>>a>>b>>c,a||b||c)w[a][b]=c;//多個數等於0的時候可以這樣簡化
如何處理不能走的
1給予一個相反的值,max給個-INF,min給個INF,求數量給了0,使其必定不是最優解
2控制更新,只有滿足條件時才更新狀態
數字三角形模型DP經驗
雙向的可以考慮反過來轉為兩次同向的
可以把每條路徑作為研究物件
重點思考最後一步是怎麼走的