P1220 关路灯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题意:
有一排路灯,老张有一个初始位置,他要把所有路灯都关掉。每个路灯都有各自的功率和坐标,问你关掉所有路灯所消耗的最小电力是什么
思路:
首先,让我们求最小电力,无非就这三种思路:贪心,二分,dp
这道题用的是dp
dp题首先要设状态,然后去转移
在设状态时,我们考虑原问题,题目中所给的决策,和状态所特有的属性
首先看原问题,我们要求的是这个关闭完这个区间所有的灯所消耗的最小电力
因此我们意识到这个是个区间dp
所以我们先尝试着设状态为f[i][j],表示关闭区间[i,j]中所有路灯要消耗的最小电力
然后我们去看题目中所给的决策,它说这个老张有两种决策:继续往下走和折返走。那么我们考虑把决策纳入我们设的状态中。我们尝试设f[i][j][0]表示关闭完这个区间后折返回去关闭其他区间的灯的要消耗的最小电力,f[i][j][1]表示关闭完这个区间后继续往右去关闭其他区间的灯要消耗的最小电力。
仔细观察老张走的路径,不难发现题目隐藏的性质:在关闭完所有灯后,老张只有可能站在左端点或右端点。站在左端点就是我们说的接下来折返回去去关闭其他灯,因此这种情况就是f[i][j][0],站在右端点就是接下来继续往右走取关闭其他灯,这种情况就是f[i][j][1]
设立好状态之后,我们去状态转移
状态转移时我们考虑最后一步决策和状态的一些属性
首先我们看f[i][j][0],它表示关闭完[i,j]的灯后老张站在左端点要折返回去关闭其他灯所消耗最小的电力,它只能从i+1这个位置转移而来。而i+1这个位置的状态也有两个属性:最终位置在左端点和最终位置在右端点,那么i位置的状态右由这两个属性取min得来
第一种情况(从[i+1,j]的左端点转移):
那就是从i+1位置转移到i位置所消耗的最小电力
众所周知:电量=时间*功率
老张的速度很慢,只有1m/s,i+1位置到i位置所花的时间就是loc[i+1]-loc[i]
功率就是除了区间[i+1,j]外剩下这么多灯的功率之和,即区间[1,i][j+1,n]的灯的功率之和
这个和只需要前缀和维护即可,即s[i+1]+s[n]-s[j]
那么剩下的第二种情况(从[i+1,j]的右端点转移)以此类推即可
f[i][j][1]的转移方程也是类推即可
状态转移方程为:
f[l][r][0]=min(f[l+1][r][0]+(loc[l+1]-loc[l])*(s[l]+s[n]-s[r]),f[l+1][r][1]+(loc[r]-loc[l])*(s[l]+s[n]-s[r]));
f[l][r][1]=min(f[l][r-1][1]+(loc[r]-loc[r-1])*(s[l-1]+s[n]-s[r-1]),f[l][r-1][0]+(loc[r]-loc[l])*(s[l-1]+s[n]-s[r-1]));
Code:
#include
using namespace std;
const int mxn=5e2+10;
int n,c;
int loc[mxn],a[mxn],s[mxn],f[mxn][mxn][2];
int main(){
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++) scanf("%d%d",&loc[i],&a[i]),s[i]=s[i-1]+a[i];
memset(f,0x3f,sizeof(f));
f[c][c][0]=f[c][c][1]=0;
for(int i=1;i
总结:
1.dp题首先设状态,然后状态转移
2.设状态时需要重点考虑的:原问题,决策,状态属性
3.状态转移需要重点考虑的:最后一步决策,状态属性
4.dp题取最大Or最小时,要把数组初始化为无穷大Or无穷小,然后根据题目的条件进行特殊情况的初始化