首页 > 其他分享 >【笔记】入门DP(Ⅱ)

【笔记】入门DP(Ⅱ)

时间:2022-08-31 20:34:03浏览次数:82  
标签:f2 入门 子段 int max 笔记 include DP 200005

0X00 P1433 吃奶酪

状压 \(DP\),把经过的点压缩成01串。若第 \(i\) 位为 \(0\) 表示未到达,为 \(1\) 则表示已到达。

用 \(f[i][j]\) 表示以 \(i\) 为起点,经过 \(j\) 所含 \(1\) 位置的所有点的最小距离。

先预处理出点两两之间的距离,记为 \(dis[i][j]\),初始化 \(f\) 数组为极大值(\(memset(f,127,sizeof(f))\) 可以为浮点数赋极大值)。将所有的 \(f[i][1<<(i-1)]\) 赋为 \(dis[0][i]\),也就是原点到它的距离。

转移方程:\(f[i][k]=min(f[i][k],f[j][k-(1<<(i-1))]+dis[i][j])\)

注意,有前提条件,就是 \(k\&(1<<(i-1)) = 0\),\(k\&(1<<(j-1)) = 0\),意为当前的01串 \(k\) 包含了 \(i\) 点和 \(j\) 点。同时要 \(i \ne j\),这个很好理解。

Code:

#include<bits/stdc++.h>
using namespace std;
double x[20],y[20],dis[20][20],f[20][50005],ans=1e9;
int n;
double calc(int i,int j){
	return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
void pre(){
	for(int i=0;i<n;i++){
		for(int j=i+1;j<=n;j++) dis[i][j]=dis[j][i]=calc(i,j);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
	memset(f,127,sizeof(f));
	pre();
	for(int i=1;i<=n;i++) f[i][1<<(i-1)]=dis[0][i];
	for(int k=1;k<(1<<n);k++){
		for(int i=1;i<=n;i++){
			if((k&(1<<(i-1)))==0) continue;
			for(int j=1;j<=n;j++){
				if(i==j) continue;
				if((k&(1<<(j-1)))==0) continue;
				f[i][k]=min(f[i][k],f[j][k-(1<<(i-1))]+dis[i][j]);
			}
		}
	} 
	for(int i=1;i<=n;i++) ans=min(ans,f[i][(1<<n)-1]);
	printf("%.2lf",ans);
	return 73;
}

0X01 P1775 石子合并(弱化版)

记 \(f[i][j]\) 表示 \(i\) 到 \(j\) 一段区间合并的最小花费。

可以考虑将一个区间 \(i\) ~ \(j\) 从任意位置 \(k\) 分成两段,再合并的最小值。

可以写出转移方程:

\(f[i][j]=\min \{\) \(f[i][k]+f[k+1][j]\) \(\}\) \(+\sum_{x=i}^{j} a[x]\) \((i\le k\le j)\)

其中 \(\sum_{x=i}^{j} a[x]\) 可以用前缀和求出。

因为求最小值,所以初始化 \(f\) 为极大值,同时将 \(f[i][i]\) 赋为 \(0\)。

Code:

#include<bits/stdc++.h>
using namespace std;
int n,f[1005][1005],a[1005],sum[1005];
int main(){
	scanf("%d",&n);
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		f[i][i]=0;
		sum[i]=sum[i-1]+a[i];
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i<=n-len+1;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++){
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
			}
		}
	}
	printf("%d",f[1][n]);
	return 76;
}

0X02 P1799 数列

记 \(f[i][j]\) 表示前 \(i\) 个数,删掉 \(j\) 个时匹配的最大个数。

首先考虑直接删掉这个数, \(f[i][j]=f[i-1][j-1]\),相当于不变。注意:当 \(j>0\) 时才考虑这一种情况。

其次,考虑不删这个数。当 \(a[i]=i-j\) 是,这个数刚好是匹配的。所以 \(f[i][j]=max(f[i][j],f[i-1][j]+(a[i]==i-j))\)

最后答案是所有 \(f[i][j]\) 中的最大值。

Code:

#include<bits/stdc++.h>
using namespace std;
int n,a[1005],f[1005][1005],ans;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){
		for(int j=0;j<i;j++){
			if(j>0) f[i][j]=f[i-1][j-1];
			f[i][j]=max(f[i][j],f[i-1][j]+(a[i]==i-j));
			ans=max(ans,f[i][j]);
		}
	}
	printf("%d",ans);
	return 111;
}

0X03 P1107 [BJWC2008]雷涛的小猫

记 \(f[i][j]\) 表示小猫在第 \(i\) 层(设高度为 \(h\) 为第 \(1\) 层,高度为 \(1\) 为第 \(h\) 层),第 \(j\) 棵树上时,最多可以吃到的柿子数量。

\(a[i][j]\) 表示第 \(i\) 棵树第 \(j\) 层有几个柿子,所以读入时这样记(为配合 \(f\),有点别扭):

for(int j=1;j<=x;j++) scanf("%d",&y),a[i][h-y+1]++;

两种转移:

  • 在同一颗树上下降 \(1\) 单位高度:\(f[i][j]=f[i-1][j]+a[j][i]\)
  • 从不同的树下来,下降 \(Delta\) 单位高度:\(f[i][j]=max(f[i][j],f[k][i-de]+a[j][i])\)。有前提条件:\(i>delta\)。

但这样时间复杂度 \(O(n^3)\),显然 \(TLE\)。考虑优化,\(f[k][i-de]\) 这一维可以用 \(mx[i-de]\) 代替,其中 \(mx[i-de]\) 表示 \(\max \{\) \(f[k][i-de]\) \(\}\),是可以边算边更新的。

Code:

#include<bits/stdc++.h>
using namespace std;
int n,h,de,x,y,f[2005][2005],mx[2005];
int a[2005][2005],ans;
int main(){
	scanf("%d%d%d",&n,&h,&de);
	for(int i=1;i<=n;i++){
		scanf("%d",&x);
		for(int j=1;j<=x;j++) scanf("%d",&y),a[i][h-y+1]++;
	}
	for(int i=1;i<=h;i++){
		for(int j=1;j<=n;j++){
			f[i][j]=f[i-1][j]+a[j][i];
			if(i>de) f[i][j]=max(f[i][j],mx[i-de]+a[j][i]);
			mx[i]=max(mx[i],f[i][j]);
		}
	}
	for(int i=1;i<=n;i++) ans=max(ans,f[h][i]);
	printf("%d",ans);
	return 118;
}

0X04 P1115 最大子段和

\(f[i]\) 表示到 \(i\) 为止最大子段和。

转移:\(f[i]=max(f[i-1],0)+a[i]\)

答案为 \(\max \{\) \(f[i]\) \(\}\)

Code:

#include<bits/stdc++.h>
using namespace std;
int n,a[200005],f[200005],ans=-1e9;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	f[1]=a[1];
	for(int i=2;i<=n;i++) f[i]=max(0,f[i-1])+a[i],ans=max(ans,f[i]);
	printf("%d",ans);
	return 101;
}

0X05 环状最大子段和(暂无例题)

分两种情况讨论。

  • 子段的头尾在 \(1\) ~ \(n\) 范围内:按上面做。
  • 子段横跨了头尾,最大和即为数列总和减去头尾在 \(1\) ~ \(n\) 范围内的最小子段和,求法相似。

记 \(f1\) 为头尾在 \(1\) ~ \(n\) 范围内最大子段和:\(f1[i]=max(f1[i-1]+a[i],a[i])\)

记 \(f2\) 为头尾在 \(1\) ~ \(n\) 范围内最小子段和:\(f2[i]=min(f2[i-1]+a[i],a[i])\)

初始化 \(f1[1]=f2[1]=a[1]\)

Code:

#include<bits/stdc++.h>
using namespace std;
int f1[200005],f2[200005];
int n,a[200005],sum,mx,mn;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];
	mx=mn=f1[1]=f2[1]=a[1];
	for(int i=2;i<=n;i++) f1[i]=max(f1[i-1]+a[i],a[i]),mx=max(mx,f1[i]);
	for(int i=2;i<=n;i++) f2[i]=min(f2[i-1]+a[i],a[i]),mn=min(mn,f2[i]);
	printf("%d",max(mx,sum-mn));
	return 67;
}

0X06 P2642 双子序列最大和

对于每个 \(k(2<k<n)\),我们让它作隔点,计算出其左边的最大子段和与右边最大子段和,相加,最后取 \(\max\) 即可。

每个点左侧的最大子段和记 \(f1[i]\),右侧的最大子段和记 \(f2[i]\),都可以 \(O(n)\) 预处理。

Code:

#include<bits/stdc++.h>
#define ll long long 
using namespace std;
ll f1[1000005],f2[1000005];
ll n,a[1000005],ans=-1e18;
int main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	f1[1]=a[1],f2[n]=a[n];
	for(int i=2;i<=n;i++) f1[i]=max(f1[i-1]+a[i],a[i]);
	for(int i=2;i<=n;i++) f1[i]=max(f1[i],f1[i-1]);
	for(int i=n-1;i>=1;i--) f2[i]=max(f2[i+1]+a[i],a[i]);
	for(int i=n-1;i>=1;i--) f2[i]=max(f2[i],f2[i+1]);
	for(int i=2;i<n;i++) ans=max(ans,f1[i-1]+f2[i+1]);
	printf("%lld",ans);
	return 89;
}

0X07 P1121 环状最大两段子段和

环状最大子段和双子序列最大和 的结合体。

需要注意的是,这题中两个子段可以去相邻的,所以更麻烦一些。

  • 首先,两子段的头尾在 \(1\) ~ \(n\) 范围内,可以按原来做,只要最后求答案改成可以相邻即可:
for(int i=1;i<n;i++) mx=max(mx,f1[i]+f2[i+1]);
  • 其次,两子段其中一段横跨了头尾,这时不用考虑相邻,因为相邻就会变成第一种情况。只要分类讨论,算一遍原数组和原数组最小子段和,整体后移一位的数组的最小子段和,取 \(\min\) 与 \(sum\) 作差即可。

Code:

#include<bits/stdc++.h>
using namespace std;
int f1[200005],f2[200005],f3[200005],f4[200005];
int n,a[200005],sum,mx=-2e9,mn;
int mmn(int k,int n){
	int tmp=2e9;
	f3[1]=a[1+k],f4[n]=a[n+k];
	for(int i=2;i<=n;i++) f3[i]=min(f3[i-1]+a[i+k],min(a[i+k],0));
	for(int i=2;i<=n;i++) f3[i]=min(f3[i-1],f3[i]);
	for(int i=n-1;i>0;i--) f4[i]=min(f4[i+1]+a[i],min(a[i],0));
	for(int i=n-1;i>0;i--) f4[i]=min(f4[i+1],f4[i]);
	for(int i=2;i<n;i++) tmp=min(tmp,f3[i-1]+f4[i+1]);
	return tmp;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];
	f1[1]=a[1],f2[n]=a[n];
	for(int i=2;i<=n;i++) f1[i]=max(f1[i-1]+a[i],a[i]);
	for(int i=2;i<=n;i++) f1[i]=max(f1[i],f1[i-1]);
	for(int i=n-1;i>=1;i--) f2[i]=max(f2[i+1]+a[i],a[i]);
	for(int i=n-1;i>=1;i--) f2[i]=max(f2[i],f2[i+1]);
	for(int i=1;i<n;i++) mx=max(mx,f1[i]+f2[i+1]);
	mn=min(mmn(0,n-1),mmn(1,n-1));
	printf("%d",max(mx,sum-mn));
	return 89;
}

标签:f2,入门,子段,int,max,笔记,include,DP,200005
From: https://www.cnblogs.com/binary1110011/p/16644428.html

相关文章

  • Python入门系列(六)一篇学会python函数
    函数函数是只在调用时运行的代码块。defmy_function():print("Hellofromafunction")my_function()信息可以作为参数传递到函数中。defmy_function(fname):......
  • 2022-08-31 第五组 赖哲栋 学习笔记
    JSPJSP脚本片段:用于在JSP页面写java代码<%%><%intnum=0;num++;System.out.println(num);//向页面打印输出out.print(num);%>注意事项......
  • Python自学笔记11-函数的定义和调用
    函数是组织代码的非常有效的方式,有了函数,我们就可以编写大规模的项目。可以说,函数是组织代码的最小单元。Python函数的定义函数是代码封装的一种手段,函数中包含一段可以......
  • 2022-8-31 第一组 (≥▽≤) 学习笔记
    目录1.JSPJSP表达式JSP声明片段JSP的指令标识JSP标签内置标签JSTL标签自定义标签JSP的作用域2.EL表达式EL表达式的内置作用域对象EL表达式的缺陷面试题1.JSPJSP脚本片段:......
  • 20220829 第一组 于芮 Vue坏人Tomcat入门
     小白成长记——第三十七天    这几天的主要学习内容就是Vue以及简单的Tomcat在ideal中的配置,总体来说说学习内容很多,每天都很充实,时间都用来学习,整个人都很开心......
  • HTML入门2(学习Head First HTML与CSS 第2版)
    <a>元素的内容会成为Web页面中可单击的文本。href属性告诉浏览器链接的目标文件。<ahref="链接地址">链接名称</a>1.一个页面链接到另一个页面,要使用<a>标签。2.<a>元......
  • RabbitMQ 入门系列:9、扩展内容:死信队列:真不适合当延时队列。
    系列目录RabbitMQ入门系列:1、MQ的应用场景的选择与RabbitMQ安装。RabbitMQ入门系列:2、基础含义:链接、通道、队列、交换机。RabbitMQ入门系列:3、基础含义:持久化、......
  • ThreadPoolExecutor 线程池的使用
    ThreadPoolExecutor这个类是JDK中的线程池类,继承自Executor,里面有一个execute()方法,用来执行线程,线程池主要提供一个线程队列,队列中保存着所有等待状态的线程。package......
  • STM32F769NI-Discovery开发笔记(二)UART
    开发环境:开发板:STM32F769NI-DiscoveryKEIL版本:5.33STM32CubeMX版本:6.3.0 本篇主要讲STM32F769NI的串口实现STM32F769NI-Discovery开发板的usb接口带有stlink与串口,......
  • 2 计算模型与复杂性类 | 密码协议课程笔记
    1计算模型1:图灵机1.1图灵机的定义图灵机是一个简洁的计算模型。我们可以将图灵机视为拥有一个无限长、可以双向移动的工作带的有限自动机。在初始阶段,工作带开始的几......