P9541题解

· · 题解

Update 2023.8.16:

重新写了一份更格式化,观赏性更强的代码。

题意

如果你没有做过 P1216 数字三角形,请先去看看。

本题其实就是 P1216 的升级版,在原本的题目上做出了一下改编:

然后问你从顶部走到底部,路上经过的数字和最大是多少,此时所用的消耗值最小为多少。

(下文说的所有旋转都指顺时针旋转)

分析

不妨单独分析一下这两个新增的条件:

旋转原三角形后无非只存在三种可能,分别为旋转了 0 度,120 度,240 度后的结果,消耗值分别为 0n2 \times n。我们在后续的操作中可以对这三类进行分类讨论。

交换三角形中同一层的两个数,这两个数中必定存在且仅存在 1 个本层最大的数

因为任意两个数交换的消耗值都是 1,跟其他数交换肯定无法比跟最大值交换好,所以其中至少有一个本层最大的数。

同时如果这两个数都是本层最大的数,那就没有交换的必要了,因此交换的两数中仅会存在 1 个本层最大的数。

因此我们可以对旋转后的三角形进行分类讨论,预处理出每层的最大值,然后就是常规的 dp 啦!

实现

dp_{i,j} 为走到第 i 行,第 j 列时经过数字和的最大值。

$m_i$ 为旋转后第 $i$ 行的最大值 ------------------------- 对于旋转 $0$ 度的三角形,明显有: $$dp_{i,j}=\max(dp_{i-1,j-1},dp_{i-1,j})+m_i$$ 然后更新 $cost_{i,j}$ 为 $cost_{i-1,j-1}$ 或 $cost_{i-1,j}$。 最后如果 $dp_{i,j} \neq m_i$,$cost_{i,j} \rightarrow cost_{i,j}+1$。 -------------------------- 对于旋转 $240$ 度的三角形,我们不需要真正去旋转它,只需要注意它与原三角形的相对位置关系,便可以直接在原三角形上 DP。 容易发现此时原三角形的列变为了行,$m_j$ 就相当于原三角形第 $j$ 列的最大值,有: $$dp_{i,j}=\max(dp_{i,j+1},dp_{i+1,j+1})+m_j$$ 注意上式中的 $i$ 和 $j$ 都要从大到小枚举,外层循环的是 $j$,以防止产生后效性。 然后更新 $cost_{i,j}$ 为 $cost_{i,j+1}$ 或 $cost_{i+1,j+1}$。 最后如果 $dp_{i,j} \neq m_j$,$cost_{i,j} \rightarrow cost_{i,j}+1$。 同时要注意,旋转三角形也会产生消耗值,所以这里记录的 $cost$ 数组不是消耗值的真实值,加上 $2 \times n$ 后才是。 -------------------------- 对于旋转 $120$ 度的三角形,同样只需要注意它与原三角形的相对位置关系,便可以直接在原三角形上 DP。 此时的一层相当于原三角形一条斜线,很容易发现一条斜线上的 $i-j$ 是一个定值,且两两互不相同。 我们当然可以直接拿 $i-j$ 作为本层 $m$ 数组的下标,但因为上文中规定 $m_i$ 为旋转后第 $i$ 行的最大值,第 $k$ 层的 $i-j$ 实际上并不等于 $k$,所以这里也可以算出 $k$ 和 $i$,$j$ 之间的关系: $$k=n-i+j$$ $m_{n-i+j}$ 就相当于旋转后第 $n-i+j$ 层的最大值,有: $$dp_{i,j}=\max(dp_{i,j-1},dp_{i+1,j})+m_{n-i+j}$$ 注意上式中的 $i$ 要从大到小枚举,$j$ 要从小到大枚举,外层循环的是 $i$,以防止产生后效性。 然后更新 $cost_{i,j}$ 为 $cost_{i,j-1}$ 或 $cost_{i+1,j}$。 最后如果 $dp_{i,j} \neq m_{n-i+j}$,$cost_{i,j} \rightarrow cost_{i,j}+1$。 同样的,这里记录的 $cost$ 数组也不是消耗值的真实值,加上 $n$ 后才是。 ## 代码 ```cpp #include<bits/stdc++.h> #define int unsigned long long #define N 2005 using namespace std; int n,y,z,a[N][N],dp[N][N],cost[N][N],maxn[N],ans,tot; void Dy_Pr(int i,int j,int i1,int j1,int i2,int j2,int line,int extra){ // i 和 j 代表现在所处的坐标 // (i1,j1) 和 (i2,j2) 是可以转移到 (i,j) 的两个坐标 // line 表示一层 // extra 表示旋转产生的消耗值 if(dp[i1][j1]>dp[i2][j2]||(dp[i1][j1]==dp[i2][j2]&&cost[i1][j1]<cost[i2][j2])){ cost[i][j]+=cost[i1][j1]; dp[i][j]=dp[i1][j1]; }else{ cost[i][j]+=cost[i2][j2]; dp[i][j]=dp[i2][j2]; } dp[i][j]+=maxn[line]; if(a[i][j]!=maxn[line]) cost[i][j]++; if(ans<dp[i][j]||(ans==dp[i][j]&&cost[i][j]+extra<tot)){ ans=dp[i][j]; tot=cost[i][j]+extra; } } void solve(int op){ for(int i=1;i<=n;i++){ maxn[i]=0; for(int j=1;j<=n;j++){ dp[i][j]=0,cost[i][j]=0; } } for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ if(op==1) maxn[i]=max(maxn[i],a[i][j]); if(op==2) maxn[j]=max(maxn[j],a[i][j]); if(op==3) maxn[i-j]=max(maxn[i-j],a[i][j]); } } if(op==1){ for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ Dy_Pr(i,j,i-1,j-1,i-1,j,i,0); } } } if(op==2){ for(int j=n;j;j--){ for(int i=n;i>=j;i--){ Dy_Pr(i,j,i,j+1,i+1,j+1,j,2*n); } } } if(op==3){ for(int i=n;i;i--){ for(int j=1;j<=i;j++){ Dy_Pr(i,j,i,j-1,i+1,j,i-j,n); } } } } signed main(){ ios::sync_with_stdio(false); cin>>n; for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ cin>>a[i][j]; } } solve(1);//旋转 0 度 solve(2);//旋转 240 度 solve(3);//旋转 120 度 cout<<ans<<" "<<tot; return 0; } ``` 老代码: ```cpp #include<bits/stdc++.h> #define int unsigned long long #define N 2005 using namespace std; int n,y,z,a[N][N],dp[N][N],cost[N][N],maxn[N],ans,tot; signed main(){ ios::sync_with_stdio(false); cin>>n; for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ cin>>a[i][j]; } } //不旋转的情况: for(int i=1;i<=n;i++){ maxn[i]=0; for(int j=1;j<=n;j++){ dp[i][j]=0,cost[i][j]=0; } }//初始化 for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ maxn[i]=max(maxn[i],a[i][j]); } }//预处理 for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ if(dp[i-1][j-1]>dp[i-1][j]||(dp[i-1][j-1]==dp[i-1][j]&&cost[i-1][j-1]<cost[i-1][j])){ cost[i][j]+=cost[i-1][j-1]; dp[i][j]=dp[i-1][j-1]; }else{ cost[i][j]+=cost[i-1][j]; dp[i][j]=dp[i-1][j]; } dp[i][j]+=maxn[i]; if(a[i][j]!=maxn[i]) cost[i][j]++; if(ans<dp[i][j]||(ans==dp[i][j]&&cost[i][j]<tot)){//更新答案 ans=dp[i][j]; tot=cost[i][j]; } } } //旋转 240 度的情况: for(int i=1;i<=n;i++){ maxn[i]=0; for(int j=1;j<=n;j++){ dp[i][j]=0,cost[i][j]=0; } }//初始化 for(int j=1;j<=n;j++){ for(int i=j;i<=n;i++){ maxn[j]=max(maxn[j],a[i][j]); } }//预处理 for(int j=n;j;j--){ for(int i=n;i>=j;i--){//注意这里的循环顺序 if(dp[i][j+1]>dp[i+1][j+1]||(dp[i][j+1]==dp[i+1][j+1]&&cost[i][j+1]<cost[i+1][j+1])){ cost[i][j]+=cost[i][j+1]; dp[i][j]=dp[i][j+1]; }else{ cost[i][j]+=cost[i+1][j+1]; dp[i][j]=dp[i+1][j+1]; } dp[i][j]+=maxn[j]; if(a[i][j]!=maxn[j]) cost[i][j]++; if(ans<dp[i][j]||(ans==dp[i][j]&&cost[i][j]+n*2<tot)){//更新答案 ans=dp[i][j]; tot=cost[i][j]+n*2;//别忘了加上旋转产生的消耗值 } } } //旋转 120 度的情况: for(int i=1;i<=n;i++){ maxn[i]=0; for(int j=1;j<=n;j++){ dp[i][j]=0,cost[i][j]=0; } }//初始化 for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ maxn[i-j]=max(maxn[i-j],a[i][j]); } }//预处理(这里为了方便就直接拿 i-j 作为数组下标了) for(int i=n;i;i--){ for(int j=1;j<=i;j++){//注意这里的循环顺序 if(dp[i][j-1]>dp[i+1][j]||(dp[i][j-1]==dp[i+1][j]&&cost[i][j-1]<cost[i+1][j])){ cost[i][j]+=cost[i][j-1]; dp[i][j]=dp[i][j-1]; }else{ cost[i][j]+=cost[i+1][j]; dp[i][j]=dp[i+1][j]; } dp[i][j]+=maxn[i-j]; if(a[i][j]!=maxn[i-j]) cost[i][j]++; if(ans<dp[i][j]||(ans==dp[i][j]&&cost[i][j]+n<tot)){//更新答案 ans=dp[i][j]; tot=cost[i][j]+n;//别忘了加上旋转产生的消耗值 } } } cout<<ans<<" "<<tot; return 0; } ```