#A.关路灯
Description
某一村庄在一条路线上安装了n盏路灯,每盏灯的功率(单位时间的耗电量)有大有小。老张就住在这条路中间某 一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。为了给村里节省电费,老张记录下了每盏 路灯的位置和功率,他每次关灯时也都是尽快地去关,但是老张不知道怎样去关灯才能够最节省电。他每天都是在 天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。开始他以为,先算一下左边路灯的总功率 ,再算一下右边路灯的总功率,然后选择先关掉功率大的一边,再回过头来关掉另一边的路灯,这样可以最省电。 而事实并非如此,因为在关的过程中适当地调头有可能会更省一些。现在已知老张走的速度为1米/秒;每个路灯的 位置(是一个整数,即距路线起点的距离,单位:米);以及功率(W),老张关灯所用的时间很短而可以忽略不 计。请你为老张编一程序来安排关灯的顺序,使从老张开始关灯时刻算起所有灯消耗电最少(灯关掉后便不再消耗 电了)。
Format
Input
第1行是两个数字n和c,分别表示路灯数和老张所处位置的路灯号;
第2行至第n+1行,每行有两个整数。
其中第k+1行的第一个整数表示第k盏灯离路线起点的距离,第二个整数表示第k盏灯的功率。
以上n+1行中,每行的两个整数之间都有一个空格分隔。
1≤n≤1000,求得的最小耗电量不大于1×10^8
Output
只有一行,该行只有一个整数,表示求得的最少耗电量。(单位:J,1J=1W·秒)。
Samples
输入数据 1
5 3 2 10 3 20 5 20 6 30 8 10
输出数据 1
270
Hint 此时关灯顺序为34215,不必输出这个关灯顺序 有5盏灯,老张从第3盏灯开始关灯,最小耗电量 =1*30+4*20+5*10+11*10=270
解题思路
经过第一章的学习,相信大家对区间DP都有所熟悉了,接下来这一章节是区间DP的进阶练习,建议熟读完第一章节再来看。
Dp状态转移方程
这道题,我们要求最小耗电量,还是熟悉的假设法,假设我们把第i盏灯关了,其他灯就不鸟它,那每秒的耗电量就是 时间*耗电量总和,得出了只管一盏灯的情况,那如果只管两盏灯,那么第二盏灯的耗电量是每次加上时间,也就是它的耗电量*老张到这来的用时,其中还有一点,就是在DP时,要开三维,第三维表示你停在那个点,我们为了节省空间,把第三维简化成,0表示关掉了区间[i~j]的路灯,停在了i点上,1表示停在j点上。
利用这一特点,我们分几种情况推一下状态转移公式:
1、的转换:
这就是由点j和点i+1到点i的方案。
2、的转换:
这下问题就解决了一半,就只需要算出的值。
的值是由什么决定的呢?由于老张每秒行走1米,于是
就是
。
耗电量前缀和
可我们很快发现这种方法有个BUG,就是你在关路灯的时间,其他未关闭的路灯依旧在耗电,我们却没有把那部分加进去!
这时候,我们就需总结下规律,每次,我们关掉区间[i~j]的路灯后,累加的耗电量是怎么产生的?是由区间[1~i-1]和区间[j+1~n]产生的,我们就只需将所有灯的耗电量计算一个前缀和,就可以得到区间[1~i-1]和区间[j+1~n]的每秒总耗电量,再乘以就可以了,我们将前缀和数组定义成sum[i],那么公式就是这样的:
核心DP代码就是这样的:
rep(len,2,n){//枚举长度
rep(i,1,n){//左端点
int j=i+len-1;//右端点
int a1=dp[i+1][j][0]+(a[i+1]-a[i])*(sum[n]-sum[j]+sum[i]);
int a2=dp[i+1][j][1]+(a[j]-a[i])*(sum[n]-sum[j]+sum[i]);
int b1=dp[i][j-1][1]+(a[j]-a[j-1])*(sum[n]-sum[j-1]+sum[i-1]);
int b2=dp[i][j-1][0]+(a[j]-a[i])*(sum[n]-sum[j-1]+sum[i-1]);
dp[i][j][0]=min(a1,a2);
dp[i][j][1]=min(b1,b2);
}
}
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define ll long long
using namespace std;
int n,c,a[100005],t,sum[1000005],dp[1005][1005][2];
int main()
{
scanf("%d%d",&n,&c);
rep(i,1,n){
scanf("%d%d",&a[i],&t);
sum[i]=sum[i-1]+t;
}
memset(dp,63,sizeof dp);
dp[c][c][1]=dp[c][c][0]=0;
rep(len,2,n){
rep(i,1,n){
int j=i+len-1;
int a1=dp[i+1][j][0]+(a[i+1]-a[i])*(sum[n]-sum[j]+sum[i]);
int a2=dp[i+1][j][1]+(a[j]-a[i])*(sum[n]-sum[j]+sum[i]);
int b1=dp[i][j-1][1]+(a[j]-a[j-1])*(sum[n]-sum[j-1]+sum[i-1]);
int b2=dp[i][j-1][0]+(a[j]-a[i])*(sum[n]-sum[j-1]+sum[i-1]);
dp[i][j][0]=min(a1,a2);
dp[i][j][1]=min(b1,b2);
}
}
printf("%d",min(dp[1][n][0],dp[1][n][1]));
return 0;
}
#B.「一本通 5.1 练习 1」括号配对
题目描述
Hecy 又接了个新任务:BE 处理。BE 中有一类被称为 GBE。
以下是 GBE 的定义:
- 空表达式是 GBE
- 如果表达式
A
是 GBE,则[A]
与(A)
都是 GBE- 如果
A
与B
都是 GBE,那么AB
是 GBE下面给出一个 BE,求至少添加多少字符能使这个 BE 成为 GBE。
输入格式
输入仅一行,为字符串 BE。
输出格式
输出仅一个整数,表示增加的最少字符数。
输入数据 0
[])
Copy
输出数据 0
1
Copy
数据范围与提示
对于 100%100% 的数据,输入的字符串长度小于 100100。
解题思路
所有DP的基本思路讲解
这道题的状态转移方程其实并不复杂,我们要记住DP的基本,简单来说,就是把一个难得问题,转换成N个容易的子问题来分开解决,逐个击破,以达到解决难题的目的,不管什么题,要搞懂这个问题是由哪个子问题的答案转移过来的,才能解决问题。
状态转移方程
我们可以把这一段字符看成一段线段,就有两种情况:
(额,手画有点丑……)
1、如果i点和j点上的括号可以配对,就可以转化成区间[i+1~j-1]的子问题加上i点与j点的长度2:
2、枚举点k,将问题转换成子问题区间[i~k]和区间[k+1~j]:
这样一来,是不是非常简单了?
rep(len,2,n){
rep(i,1,n){
int j=i+len-1;
if(c[i]=='('&&c[j]==')'||c[i]=='['&&c[j]==']')dp[i][j]=max(dp[i][j],dp[i+1][j-1]+2);
rep(k,i,j-1){
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
}
}
}
strlen()函数
在输入时,要注意,用scanf读入,可以写“字符数组名称+1”,改变下标,在用一个变量存储strlen()函数的返回值,为字符数组长度。
scanf("%s",(c+1));
int n=strlen(c+1);
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define ll long long
using namespace std;
int dp[1001][1001];
char c[1005];
int main()
{
scanf("%s",(c+1));
int n=strlen(c+1);
rep(len,2,n){
rep(i,1,n){
int j=i+len-1;
if(c[i]=='('&&c[j]==')'||c[i]=='['&&c[j]==']')dp[i][j]=max(dp[i][j],dp[i+1][j-1]+2);
rep(k,i,j-1){
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
}
}
}
printf("%d",n-dp[1][n]);
return 0;
}
#C.[SCOI2003] 字符串折叠
题目描述
折叠的定义如下:
- 一个字符串可以看成它自身的折叠。记作
S = S
X(S)
是 XX 个S
连接在一起的串的折叠。记作X(S) = SSSS…S
。- 如果
A = A’
,B = B’
,则AB = A’B’
。例如:因为3(A) = AAA
,2(B) = BB
,所以3(A)C2(B) = AAACBB
,而2(3(A)C)2(B) = AAACAAACBB
给一个字符串,求它的最短折叠。
例如
AAAAAAAAAABABABCCD
的最短折叠为:9(A)3(AB)CCD
。输入格式
仅一行,即字符串
S
,长度保证不超过 100。输出格式
仅一行,即最短的折叠长度。
样例 #1
样例输入 #1
NEERCYESYESYESNEERCYESYESYES
Copy
样例输出 #1
14
Copy
提示
一个最短的折叠为:
2(NEERC3(YES))
解题思路
我们折叠字符串的关键在于最优折叠的条件,怎样折叠才最优?怎样判断最优?这就是这道题的难点。
我们首先得枚举长度,枚举一个区间是否可以折叠,让后枚举左端点和右端点,接着在这个区间内枚举循环节,循环节具备以下几个特点:
1、循环节长度必须是整个区间长度的因数(取余后结果为0)
2、整个区间必须由若干个循环节组成。
这下大家好写了吧!
这里要注意几点,由于循环节不可能超过3位数,我们可以写个函数判断一下,再加上两个括号的长度2,取值。
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define ll long long
using namespace std;
int dp[1005][1005];
char c[10005];
bool f;
int w(int x){//判断函数
if(x>=10)return 2;
if(x>=100)return 3;
return 1;
}
int cheak(int l,int mid,int r){//检测是否为循环节
int x=l;
rep(i,mid+1,r){
if(c[i]!=c[x]){
return 0;
}
x++;
if(x>mid)x=l;
}
return 1;
}
int main()
{
scanf("%s",c+1);
int n=strlen(c+1);
rep(len,1,n){
rep(i,1,n-len+1){
int j=i+len-1;
f=true;
dp[i][j]=j-i+1;//先将最小值设为整个区间长度
rep(k,i,j-1){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);//分解子问题
}
rep(k,i,j-1){
if((j-i+1)%(k-i+1))continue;
if(cheak(i,k,j))
dp[i][j]=min(dp[i][j],dp[i][k]+w(len/(k-i+1))+2);//判断循环节,取min
}
}
}
printf("%d",dp[1][n]);
return 0;
}
第二章完,敬请期待……