CF2049D 题解
题意
给定一个 \(n\times m\) 的数字矩阵和常数 \(K\),初始位于 \((1,1)\) 点,只能通过向下或者向右走来到达 \((n,m)\) 点。
存在某种操作,可以选择任意一行 ,将其所有列元素逆时针旋转 \(1\) 个单位,这个操作可以对任意行进行任意次(下面称这个操作为“旋转”)。
设最后总操作次数为 \(x\),经过的所有元素和为 \(S\),最后的代价就是 \(K\times x+ S\),求出这个代价可能的最小值。
注意:所有“旋转”操作需要在出发之前确定,或者等效来讲,不可以在某一行上移动时,对该行进行“旋转”操作。
分析
这个问题很明显可以 DP,并且有两个显然的观察:
- 每一行的旋转与否、旋转的次数是彼此之间独立的。
- 任意一行的旋转次数不会超过 \(m-1\) 次,否则就是无用功。
那么就可以很自然的得到 DP 的定义:\(dp[i][j][k],k\in[0,m-1]\) 为 到达 \((i,j)\) 点,当前行旋转了 \(k\) 次的最小代价。
那么对于某个点 \((i,j)\),其只会从 \((i-1,j)\) 和 \((i,j-1)\) 转移。
从 \((i-1,j)\) 转移的时候,完全可以不用关心 \(i-1\) 行旋转了多少次,正如上文 “1” 所说,行与行之间的旋转操作是独立的,我只需要知道到达 \((i-1,j)\) 这个点的最小代价即可,这一过程可以通过在 DP 过程中随手记录下最小值实现。
从 \((i,j-1)\) 转移的时候,\(dp[i][j][k]\) 仅可从 \(dp[i][j-1][k]\) 转移,这一点在 “注意” 中已经指出。
那么这样一来整个流程就明了了,唯一需要再额外注意的地方在于可能会有数据溢出的情况。
Code
#include<bits/stdc++.h>
using namespace std;
int T,n,m,k;
const int N=2e5+10;
typedef long long ll;
const ll INF=1e15+1;
ll a[300][300];
inline ll get(int i,int j,int add)
{int tmp=(j+add)%m;return tmp==0?a[i][m]:a[i][tmp];
}
inline void solve()
{cin>>n>>m>>k;for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)cin>>a[i][j];vector<vector<vector<ll>>> dp(n+1,vector<vector<ll>>(m+1,vector<ll>(m+1,INF)));vector<vector<ll>>mdp(n+1,vector<ll>(m+1,INF));mdp[0][1]=mdp[1][0]=0;for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){for(int x=0;x<=m-1;++x){dp[i][j][x]=min(dp[i][j][x],mdp[i-1][j]+get(i,j,x)+1ll*x*k);//防止溢出dp[i][j][x]=min(dp[i][j][x],dp[i][j-1][x]+get(i,j,x));mdp[i][j]=min(mdp[i][j],dp[i][j][x]);}}}cout<<mdp[n][m]<<'\n';
}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>T;while(T--){solve();}
}