区间dp②

区间dp的第②篇,稍微有点难度

关路灯

题目描述

某一村庄在一条路线上安装了n盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。

为了给村里节省电费,老张记录下了每盏路灯的位置和功率,他每次关灯时也都是尽快地去关,但是老张不知道怎样去关灯才能够最节省电。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。开始他以为先算一下左边路灯的总功率再算一下右边路灯的总功率,然后选择先关掉功率大的一边,再回过头来关掉另一边的路灯,而事实并非如此,因为在关的过程中适当地调头有可能会更省一些。

现在已知老张走的速度为1m/s,每个路灯的位置(是一个整数,即距路线起点的距离,单位:m)、功率(W),老张关灯所用的时间很短而可以忽略不计。

请你为老张编一程序来安排关灯的顺序,使从老张开始关灯时刻算起所有灯消耗电最少(灯关掉后便不再消耗电了)。

输入输出格式

输入格式:

文件第一行是两个数字n(1<=n<=50,表示路灯的总数)和c(1<=c<=n老张所处位置的路灯号);

接下来n行,每行两个数据,表示第1盏到第n盏路灯的位置和功率。数据保证路灯位置单调递增。

输出格式:

一个数据,即最少的功耗(单位:J,1J=1W·s)。

输入输出样例

输入样例:       

 5 3

2 10
3 20
5 20
6 30
8 10输出样例:
270 都是一段区间,因为你走过的路的灯都会顺手关掉;所以考虑区间dp

先按顺序把灯排序,然后考虑dp[i][j]表示关掉区间[i,j]里所有灯的最小花费,但好像没法转移,应为可能我关完[i,j]里的灯后我在区间左端点,然后我要往右走去关掉右区间的灯,或者我在右端点,所以我不好表示走过的路程,于是可以加①维表示在左端点还是右端点dp[i][j][0/1]表示关掉区间里的灯在左/右端点的最小花费然后考虑转移dp[i][j][0]<-dp[i+1][j][0/1]dp[i][j][1]<-dp[i][j-1][0/1]然后花式转移方程,详见代码
#include<cstdio>
#include<cstring>

using namespace std;

int min(int a,int b)
{
    return a<b?a:b;
}
int n,c;
int wz[60],pw[60],tot[60];
int dp[60][60][2];

int main()
{
    scanf("%d%d",&n,&c);
    memset(dp,120,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&wz[i],&pw[i]);    
        tot[i]=tot[i-1]+pw[i];    //tot前缀和数组,记录tot[i]记录从1到i的耗电量
    }
    dp[c][c][1]=0;         //初始化出生点
    dp[c][c][0]=0;
    for(int k=2;k<=n;k++)    //区间大小
    {
        for(int i=1;i+k-1<=n;i++)      ///区间左右端点
        {
            int j=i+k-1;
            dp[i][j][0]=min(dp[i+1][j][0]+(wz[i+1]-wz[i])*(tot[i]+tot[n]-tot[j]),      //从区间[i+1][j]向[i][j]宽展,距离*耗电量(脚动模拟下标)
                            dp[i+1][j][1]+(wz[j]-wz[i]) * (tot[i]+tot[n]-tot[j]));
            dp[i][j][1]=min(dp[i][j-1][0]+(wz[j]-wz[i]) * (tot[i-1]+tot[n]-tot[j-1]),
                            dp[i][j-1][1]+(wz[j]-wz[j-1])*(tot[i-1]+tot[n]-tot[j-1]));
        }
    }
    printf("%d",min(dp[1][n][0],dp[1][n][1]));

}

感觉这题还好,细心处理下表ok

然后看下道题


棋盘分割

Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 15794   Accepted: 5609

Description

将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的均方差最小。
均方差,其中平均值,xi为第i块矩形棋盘的总分。
请编程对给出的棋盘及n,求出O‘的最小值。

Input

第1行为一个整数n(1 < n < 15)。
第2行至第9行每行为8个小于100的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。

Output

仅一个数,为O‘(四舍五入精确到小数点后三位)。

Sample Input

3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3

Sample Output

1.633
脚动推公式,可知要是分的每个区间的平方和最小(简单的数学)

然后发现数据很小,可以考虑dp[k][x1][y1][x2][y2]表示左上角[x1][y1]右上角[x2][y2]分了k个的时候的平方和然后可以发现他可以横切,也可以竖切,然后选切得其中一块;于是可以尝试写方程:                                                        少女祈祷中......dp[i][x][y][x1][y1]=min(dp[i][x][y][x1][y1],        min(dp[i-1][x][y][x1][k]+dp[0][x][k+1][x1][y1],dp[0][x][y][x1][k]+dp[i-1][x][k+1][x1][y1]));dp[i][x][y][x1][y1]=min(dp[i][x][y][x1][y1],      min(dp[i-1][x][y][k][y1]+dp[0][k+1][y][x1][y1],dp[0][x][y][k][y1]+dp[i-1][k+1][y][x1][y1]));

看着方程很长很乱  很油很亮很duang 其实没什么软用仔细分析,红字和蓝字表示竖着切拿右边还是左边,橙字和绿字表示横着切拿下面还是上面;然后就是一些垃圾的dp前处理
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

int dp[15][9][9][9][9];
int sum[9][9];
int n;int tot;
int main()
{

    scanf("%d",&n);
    for(int i=1;i<=8;++i)
    {
        for(int j=1;j<=8;++j)
        {
            int a;
            scanf("%d",&a);
            sum[i][j]=sum[i][j-1]+a;
            tot+=a;
        }
    }
    for(int i=2;i<=8;++i)
        for(int j=1;j<=8;++j)
        sum[i][j]+=sum[i-1][j];        //sum[i][j]表示以1,1到i,j的矩形大小
    for(int x=1;x<=8;++x)
        for(int y=1;y<=8;++y)
            for(int x1=1;x1<=8;++x1)
                for(int y1=1;y1<=8;++y1)
                 dp[0][x][y][x1][y1]=
                 (sum[x1][y1]-sum[x-1][y1]-sum[x1][y-1]+sum[x-1][y-1])
                 *(sum[x1][y1]-sum[x-1][y1]-sum[x1][y-1]+sum[x-1][y-1]);      //每个格子的值赋为自己的平方
    for(int i=1;i<n;++i)
    {
        for(int x=1;x<=8;++x)
        {
            for(int y=1;y<=8;++y)
            {
                for(int x1=x;x1<=8;++x1)
                {
                    for(int y1=y;y1<=8;++y1)
                    {
                        dp[i][x][y][x1][y1]=0x3f3f3f3f;
                        for(int k=y;k<y1;++k) dp[i][x][y][x1][y1]=min(dp[i][x][y][x1][y1],                  //刚才的方程
                        min(dp[i-1][x][y][x1][k]+dp[0][x][k+1][x1][y1],dp[0][x][y][x1][k]+dp[i-1][x][k+1][x1][y1]));
                        for(int k=x;k<x1;++k) dp[i][x][y][x1][y1]=min(dp[i][x][y][x1][y1],
                        min(dp[i-1][x][y][k][y1]+dp[0][k+1][y][x1][y1],dp[0][x][y][k][y1]+dp[i-1][k+1][y][x1][y1]));
                    }
                }
            }
        }
    }
    double ans=sqrt(dp[n-1][1][1][8][8]*1.0/n-((tot*1.0)/n*(tot*1.0)/n));        //脚动解出的式子
    printf("%0.3lf",ans);
}
    

然后一定要多做题

 

原文地址:https://www.cnblogs.com/AidenPearce/p/8467998.html

时间: 2024-10-09 08:07:36

区间dp②的相关文章

uva 10003 Cutting Sticks 简单区间dp

// uva 10003 Cutting Sticks 区间dp // 经典的区间dp // dp(i,j)表示切割小木棍i-j所需要的最小花费 // 则状态转移为dp(i,j) = min{dp(i,k) + dp(k,j) + a[j]-a[i]) // 其中k>i && k<j // a[j] - a[i] 为第一刀切割的代价 // a[0] = 0,a[n+1] = L; // dp数组初始化的时候dp[i][i+1]的值为 0,这表示 // 每一段都已经是切割了的,不

黑书例题 Fight Club 区间DP

题目可以在bnuoj.soj等OJ上找到. 题意: 不超过40个人站成一圈,只能和两边的人对战.给出任意两人对战的输赢,对于每一个人,输出是否可能是最后的胜者. 分析: 首先序列扩展成2倍,破环成链. dp[i][j]表示i和j能够相遇对打,那么dp[i][i+n]为真代表可以成为最后胜者. 枚举中间的k,若i和j都能和k相遇,且i和j至少一人能打赢k,那么i和j可以相遇. 复杂度o(n^3) 1 #include<cstdio> 2 #include<cstring> 3 usi

算法复习——区间dp

感觉对区间dp也不好说些什么直接照搬讲义了2333 例题: 1.引水入城(洛谷1514) 这道题先开始看不出来到底和区间dp有什么卵关系···· 首先肯定是bfs暴力判一判可以覆盖到哪些城市····无解直接输出···有解得话就要想想了···· 这道题关键是要发现··如果一个蓄水池所在城市可以覆盖到一些沙漠城市···那么这些沙漠城市肯定是一段····不然假设有一个城市是断开的而两边都被同一个蓄水池流出的水覆盖,这个城市四周的城市都肯定比它矮···(不理解举个反例吧···反正我举不出来)···然后就

合并石子 区间dp水题

合并石子 链接: nyoj 737 描述: 有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆.合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆.求出总的代价最小值. tags:最基本的区间dp,这题范围小,如果n大一些,还是要加个平行四边行优化. #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring&g

Luogu P2734 游戏 A Game 区间DP

P2734 游戏 A Game 题目背景 有如下一个双人游戏:N(2 <= N <= 100)个正整数的序列放在一个游戏平台上,游戏由玩家1开始,两人轮流从序列的任意一端取一个数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束.以最终得分多者为胜. 题目描述 编一个执行最优策略的程序,最优策略就是使玩家在与最好的对手对弈时,能得到的在当前情况下最大的可能的总分的策略.你的程序要始终为第二位玩家执行最优策略. 输入输出格式 输入格式: 第一行: 正整数N, 表示序列中正整数的个数

HDU-4283 You Are the One (区间DP)

Problem Description The TV shows such as You Are the One has been very popular. In order to meet the need of boys who are still single, TJUT hold the show itself. The show is hold in the Small hall, so it attract a lot of boys and girls. Now there ar

lightoj1031_区间dp

题目链接:http://lightoj.com/volume_showproblem.php?problem=1031 题目描述: 给出一个数列,两人轮流取数, 取完结束.每次可以取好多个数,但是只能从首或者尾为起点取连续的若干个.问最后两者取数和的绝对值最大为多少? 区间dp: 这道题我是在看了几份阶梯报告之后才想通的,现在想想很符合动态规划的要求 d(i, j)表示取数的人在数组i 到 j中能取的的最大值,然后中间枚举分割点, ans = max(ans, sum[k]-sum[i-1]-d

lightoj1025_区间dp

题目链接:http://lightoj.com/volume_showproblem.php?problem=1025 题目描述: 给出一个字符串,可以任意删除位置的字符,也可以删除任意多个.问能组成多少个回文串? 解题思路: 自从开始学dp,感觉自己智商一直处于离线状态.席八啊啊啊啊啊啊!今天随机到这个题目,看了好久竟然没有看出来是区间DP.知道是区间DP后立马感觉明白. 情景设定 dp[l][r] 表示 区间 [l, r] 内的回文串数目. 状态转移:dp[l][r] = dp[l][r-1

POJ 2955 Brackets (区间DP)

题意:给定一个序列,问你最多有多少个合法的括号. 析:区间DP,dp[i][j] 表示在 第 i 到 第 j 区间内最多有多少个合法的括号. 代码如下: #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <string> #include <cstdlib> #include <cmath> #include <ios

ZOJ 3537 Cake 求凸包 区间DP

题意:给出一些点表示多边形顶点的位置(如果多边形是凹多边形就不能切),切多边形时每次只能在顶点和顶点间切,每切一次都有相应的代价.现在已经给出计算代价的公式,问把多边形切成最多个不相交三角形的最小代价是多少. 思路:首先判断多边形是否是凸多边形,之后就是区间dp了. 求出凸包后,按逆时针来看. 设置dp[i][j]为从顶点i到顶点j所围成凸多边形的最优解. 枚举切点k (i < k < j) dp[i][j] = min(dp[i][k] + dp[k][j] + cost[i][k] + c