洛谷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最小用电量。
那么答案为 min(f[1][n][1],f[1][n][n])
用前缀和数组sum来记录电量,则可以在O(1)时间内算出区间电量和。
状态转移方程也不难写:

F[i][j][0]=min((a[i+1]a[i])

(sum[n]sum[j]+sum[i])+F[i+1][j][0],

(a[j]a[i])(sum[n]sum[j]+sum[i])+F[i+1][j][1])


F[i][j][1]=min((a[j]a[j1])

(sum[n]sum[j1]+sum[i1])+F[i][j1][1],

(a[j]a[i])(sum[n]sum[j1]+sum[i1])+F[i][j1][0])

(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的时候,有事没事就要来剪枝、、、
另外一定要提高检查的效率,也就是说——
使用数据结构来加速算法。

原创粉丝点击