洛谷1220 关路灯
来源:互联网 发布:ip查询软件 编辑:程序博客网 时间:2024/04/27 20:45
题目描述
某一村庄在一条路线上安装了n盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。
为了给村里节省电费,老张记录下了每盏路灯的位置和功率,他每次关灯时也都是尽快地去关,但是老张不知道怎样去关灯才能够最节省电。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。开始他以为先算一下左边路灯的总功率再算一下右边路灯的总功率,然后选择先关掉功率大的一边,再回过头来关掉另一边的路灯,而事实并非如此,因为在关的过程中适当地调头有可能会更省一些。
现在已知老张走的速度为1m/s,每个路灯的位置(是一个整数,即距路线起点的距离,单位:m)、功率(W),老张关灯所用的时间很短而可以忽略不计。
请你为老张编一程序来安排关灯的顺序,使从老张开始关灯时刻算起所有灯消耗电最少(灯关掉后便不再消耗电了)。
传送门
算法 && 代码
这道题两种做法都可以做(数据水)。
DFS
我的方法有点复杂:记录每盏灯的左边未关和右边未关(可以理解成链表),每次关路灯就更新,这样每次搜索就只有两张选择,加上最优化剪枝直接把时间压到了4 ms(最后一个点)
代码如下:
#include <algorithm>#include <iostream>#include <cstdlib>#include <cstring>#include <cstdio>using namespace std;int n;struct Light //每盏灯 { int x,w; int l,r;}a[60];bool go[60];int ans=2147483647;void Dfs(int id,int now,int sum) { //id为当前灯编号,now为当前坐标,sum为当前消耗电力 if (sum>ans) return; //剪枝 go[now]=true; //标记 bool Re=false; //是否关完 int S=0; for (int i=1;i<=n;i++) //累加答案 if (!go[i]) S+=a[i].w; if (S==0) Re=true; if (Re) { if (sum<ans) ans=sum; } if (a[now].l) { //往左走 int next_now=a[now].l; //下一个状态 int next_time=abs(a[now].x-a[next_now].x); int next_sum=0; for (int i=1;i<=n;i++) if (!go[i]) next_sum+=a[i].w; next_sum*=next_time; sum+=next_sum; //next_sum-=a[next_now].w; a[now].l=a[next_now].l; a[next_now].r=a[now].r; go[next_now]=true; Dfs(id+1,next_now,sum); //搜索 go[next_now]=false; //还原 a[now].l=next_now; a[next_now].r=now; sum-=next_sum; } if (a[now].r) { //同上,往右走 int next_now=a[now].r; int next_time=abs(a[next_now].x-a[now].x); int next_sum=0; for (int i=1;i<=n;i++) if (!go[i]) next_sum+=a[i].w; next_sum*=next_time; sum+=next_sum; //next_sum-=a[next_now].w; a[now].r=a[next_now].r; a[next_now].l=a[now].l; go[next_now]=true; Dfs(id+1,next_now,sum); go[next_now]=false; a[now].r=next_now; a[next_now].l=now; sum-=next_sum; }}int main(){ int c; scanf("%d%d",&n,&c); for (int i=1;i<=n;i++) { //输入与初始化 scanf("%d%d",&a[i].x,&a[i].w); a[i].l=i-1; a[i].r=i+1; } a[1].l=0; a[n].r=0; go[c]=true; //初始标记 Dfs(1,c,0); //搜索 printf("%d\n",ans); //输出 return 0;}
本题搜索代码较多,很麻烦,所以引出正解——
区间DP
设f[i][j][0]表示i到j的路灯、最后关i最小用电量,f[i][j][1]表示i到j的路灯、最后关j最小用电量。
那么答案为
用前缀和数组sum来记录电量,则可以在O(1)时间内算出区间电量和。
状态转移方程也不难写:
(LaTex不好换行啊。。。)
一看很复杂,其实就是在推每个状态是从哪边走过来。
程序也不难写,代码如下:
#include <algorithm>#include <iostream>#include <cstdlib>#include <cstring>#include <cstdio>using namespace std;int f[60][60][2];int p[60],w[60],sum[60];int main(){ int n,c; scanf("%d%d",&n,&c); for (int i=1;i<=n;i++) //输入+前缀和 { scanf("%d%d",&p[i],&w[i]); sum[i]=sum[i-1]+w[i]; } for (int i=1;i<=n;i++) f[i][i][0]=f[i][i][1]=sum[n]*abs(p[i]-p[c]); //边界 for (int l=2;l<=n;l++) //核心DP部分 for (int i=1;i<=n-l+1;i++) { int j=i+l-1; f[i][j][0]=min((p[i+1]-p[i])*(sum[n]-sum[j]+sum[i])+f[i+1][j][0], (p[j]-p[i])*(sum[n]-sum[j]+sum[i])+f[i+1][j][1]); f[i][j][1]=min((p[j]-p[j-1])*(sum[n]-sum[j-1]+sum[i-1])+f[i][j-1][1], (p[j]-p[i])*(sum[n]-sum[j-1]+sum[i-1])+f[i][j-1][0]); } printf("%d\n",min(f[1][n][0],f[1][n][1])); //输出 return 0;}
后记
其实考试时我是用搜索做的,结果没剪枝TLE1个点只有90分。。。
所以说在DFS的时候,有事没事就要来剪枝、、、
另外一定要提高检查的效率,也就是说——
使用数据结构来加速算法。
- 洛谷1220 关路灯
- 洛谷1220关路灯
- 【洛谷1220】关路灯
- 洛谷1220 关路灯
- 【CJOJ1603】【洛谷1220】关路灯
- 关路灯_洛谷1220_dp
- 洛谷P1220 关路灯
- 洛谷 P1220 关路灯
- 洛谷 P1220 关路灯
- 洛谷 P1220 关路灯
- 洛谷P1220 关路灯
- 洛谷 P1220 关路灯
- 洛谷 P1220 关路灯
- 关路灯 洛谷p1220
- 洛谷 P1220 关路灯
- 洛谷 [P1220] 关路灯
- 洛谷P1220,codevs1258关路灯
- 【DP】洛谷 P1220 关路灯
- eclispe 使用问题记录
- 关于win7系统下载安装msi程序是会出现windows installer错误或没有正确安装
- [Noip2012]借教室
- HDU6165-FFF at Valentine
- 了解函数式编程
- 洛谷1220 关路灯
- HDU 6097 Mindis【计算几何+反演点】
- Think Python 练习答案
- 多表连查
- 编写一个能将给定非负整数数组中的数字排列成最大数字的函数
- JavaScript之AJAX:原生ajax入门
- [设计模式](十一):观察者模式|迭代器模式|责任链模式|命令模式(四种类间行为模式)
- 【BZOJ】1257 [CQOI2007]余数之和sum 公式变形
- mysql预处理