区间DP专栏 第二章(关路灯、「一本通 5.1 练习 1」括号配对、[SCOI2003] 字符串折叠等)

#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时,要开三维,第三维表示你停在那个点,我们为了节省空间,把第三维简化成dp_{i,j,0/1},0表示关掉了区间[i~j]的路灯,停在了i点上,1表示停在j点上。

利用这一特点,我们分几种情况推一下状态转移公式:

1、dp_{i,j,0}的转换:

dp_{i,j,0}=max(dp_{i+1,j,0}+(a_{i+1}-a_i)*time,dp_{i+1,j,1}+(a_j-a_i)*time)

这就是由点j和点i+1到点i的方案。

2、dp_{i,j,1}的转换:

dp_{i,j,1}=max(dp_{i,j-1,1}+(a_j-a_{j-1})*time,dp_{i,j-1,0}+(a_j-a_i)*time)

这下问题就解决了一半,就只需要算出time的值。

time的值是由什么决定的呢?由于老张每秒行走1米,于是time就是(a_i-a_j)*1

耗电量前缀和

可我们很快发现这种方法有个BUG,就是你在关路灯的时间,其他未关闭的路灯依旧在耗电,我们却没有把那部分加进去!

这时候,我们就需总结下规律,每次,我们关掉区间[i~j]的路灯后,累加的耗电量是怎么产生的?是由区间[1~i-1]和区间[j+1~n]产生的,我们就只需将所有灯的耗电量计算一个前缀和,就可以得到区间[1~i-1]和区间[j+1~n]的每秒总耗电量,再乘以(a_i-a_j)就可以了,我们将前缀和数组定义成sum[i],那么公式就是这样的:

dp_{i,j,0}=max(dp_{i+1,j,0}+(a_{i+1}-a_i)*(sum_n-sum_j+sum_i),dp_{i+1,j,1}+(a_j-a_i)*(sum_n-sum_j+sum_i))

dp_{i,j,0}=max(dp_{i+1,j,0}+(a_{i+1}-a_i)*(sum_n-sum_{j-1}+sum_{i-1}),dp_{i+1,j,1}+(a_j-a_i)*(sum_n-sum_{j-1}+sum_{i-1}))

核心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 的定义:

  1. 空表达式是 GBE
  2. 如果表达式 A 是 GBE,则 [A] 与 (A) 都是 GBE
  3. 如果 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:

dp_{i,j}=max(dp_{i,j},dp_{i+1,j-1}+2)

2、枚举点k,将问题转换成子问题区间[i~k]和区间[k+1~j]:

dp_{i,j}=max(dp_{i,j},dp_{i,k}+dp_{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] 字符串折叠

题目描述

折叠的定义如下:

  1. 一个字符串可以看成它自身的折叠。记作 S = S
  2. X(S) 是 XX 个 S 连接在一起的串的折叠。记作 X(S) = SSSS…S
  3. 如果 A = A’B = B’,则 AB = A’B’ 。例如:因为 3(A) = AAA2(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,取min值。

代码

#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;
}

第二章完,敬请期待……

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
中描述了一个幼儿园里分配糖果的问题,每个小朋友都有自己的要求。问题的输入包括两个整数NN和KK,表示幼儿园里的小朋友数量和要满足的要求数量。接下来的KK行表示小朋友们的要求,每行有三个数字,XX,AA,BB。如果X=1,表示第AA个小朋友分到的糖果必须和第BB个小朋友分到的糖果一样多;如果X=2,表示第AA个小朋友分到的糖果必须少于第BB个小朋友分到的糖果;如果X=3,表示第AA个小朋友分到的糖果必须不少于第BB个小朋友分到的糖果;如果X=4,表示第AA个小朋友分到的糖果必须多于第BB个小朋友分到的糖果;如果X=5,表示第AA个小朋友分到的糖果必须不多于第BB个小朋友分到的糖果。这个问题可以被看作是一个差分约束系统的问题。 具体地说,可以使用差分约束系统来解决这个问题。差分约束系统是一种过给变量之间的系添加约束来求解最优解的方法。对于这个问题,我们需要根据小朋友们的要求建立约束条件,并过解决这个约束系统来得出最小的糖果数量。 在问题的输入中,X的取值范围为1到5,分别对应不同的系约束。根据这些约束,我们可以构建一个差分约束图。图中的节点表示小朋友,边表示糖果数量的系。根据不同的X值,我们可以添加相应的边和权重。然后,我们可以使用SPFA算法(Shortest Path Faster Algorithm)来求解这个差分约束系统,找到满足所有约束的最小糖果数量。 需要注意的是,在读取输入时需要判断X和Y是否合法,即是否满足X≠Y。如果X=Y,则直接输出-1,因为这种情况下无法满足约束条件。 综上所述,为了满足每个小朋友的要求,并且满足所有的约束条件,我们可以使用差分约束系统和SPFA算法来求解这个问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [【差分约束系统】【SCOI2011】糖果 candy](https://blog.csdn.net/jiangzh7/article/details/8872699)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [P3275 [SCOI2011]糖果(差分约束板子)](https://blog.csdn.net/qq_40619297/article/details/88678605)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值