题解:B3734 [信息与未来 2017] 加强版密码锁

· · 题解

题意简述

有一个 n 个拨盘的密码锁,每个拨盘的初始数字由给定的伪随机数列 R 生成(第 i 个拨盘的初始数字为 R_i \bmod 100)。每个拨盘的数字可以在 0 99 之间调整,调整规则如下:

每次拨动一个拨盘 k 次(无论是向上还是向下),需要花费 k^2 单位时间。目标是让所有拨盘的数字从左到右形成一个‌严格递增的数列‌(即前一个数字严格小于后一个数字),求达成这一目标所需的最少时间。

输入:

首先,我们既然是要通过拨动拨盘来让消耗的时间越少,那当务之急必然是算出拨动拨盘的代价。具体的,设 cost_{i,j} 表示从数字 i 拨到数字 j 所需的代价。不难发现,从数字 i 拨到数字 j,无非就是往上拨更优还是往下拨更优。根据题意模拟即可:

const int maxn=100;
int mul(int x){return x*x;}
void init()
{
    for(int i=0;i<maxn;i++)
     for(int j=0;j<maxn;j++)
      if(j>i) cost[i][j]=min(mul(j-i),mul(100+i-j));
         else cost[i][j]=min(mul(i-j),mul(100+j-i));
}

其次,既然是动态规划,那三要素可不能少。

状态

要想知道状态,我们得先知道那些因素是与答案有关系的。首先枚举到第几个拨盘一定是有关系的。因为想要知道是不是满足严格递增,你总得知道前一个是啥呀。

\\

其次,要想知道是否单调递增,那具体的数是必须得知道的,所以我们可以总结出状态。

## 边界 首先肯定要把 $dp$ 数组赋值为无穷大。接下来考虑只有一个拨盘的情况。很明显,当只有一个拨盘的时候,我们直接拨动这个拨盘即可。很容易写出代码: ```cpp for(int i=0;i<maxn;i++) dp[1][i]=cost[a[1]][i]; ``` ## 转移 接下来便是动态规划中最难的一步——状态转移。考虑到所有数据满足 $1\le N\le 100$。所以即便时 $O(n^3)$ 的 dp 也依然能过。那接下来我们可以考虑枚举一个中间变量来实现状态转移,那到底怎么转呢?考虑到第 $i$ 个拨盘的状态依赖第 $i-1$ 个拨盘的状态,那这个中间变量肯定是跟第 $i-1$ 哥拨盘相关,那可以是什么呢?显然应该是第 $i-1$ 个拨盘拨的数,不妨设该变量为 $k$,接下来就该考虑怎么写状态转移方程了。 ### 状态转移方程 首先枚举 $k\in[0,j-1]$,因为 $k$ 是第 $i-1$ 个拨盘拨的数,要想使数列单调递增,那 $k$ 必然小于 $j$,那接下来状态转移方程就很好想了。我们可以理解为先把前一个拨盘拨到 $k$,再将这一个拨盘拨到 $j$,可得状态转移方程: $$dp_{i,j}=\min^{k=0}_{k<j}\{dp_{i-1,k}+cost_{a_i,j}\}$$ # 代码 ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int maxn=100; int cost[maxn+5][maxn+5]; int a[maxn+5],n,x; int dp[maxn+5][maxn+5]; int mul(int x){return x*x;} void init() { for(int i=0;i<maxn;i++) for(int j=0;j<maxn;j++) if(j>i) cost[i][j]=min(mul(j-i),mul(100+i-j)); else cost[i][j]=min(mul(i-j),mul(100+j-i)); } //初始化cost数组 signed main() { init(); cin>>n>>x; for(int i=1;i<=n;i++) { a[i]=x%100; x=(x*6807+2831)%201701; } memset(dp,0x3f,sizeof dp); for(int i=0;i<maxn;i++) dp[1][i]=cost[a[1]][i];//初始化边界 for(int i=2;i<=n;i++) for(int j=0;j<maxn;j++) for(int k=0;k<j;k++) dp[i][j]=min(dp[i][j],dp[i-1][k]+cost[a[i]][j]);//状态转移 int ans=1e18; for(int i=0;i<maxn;i++) ans=min(ans,dp[n][i]);//计算答案,应该不用我多说了吧 cout<<ans; return 0; } ```