NOIP专题知识点1 dp

动态规划 编辑距离问题

fzoj1307
题目描述
设A 和B 是2 个字符串。要用最少的字符操作将字符串A 转换为字符串B。这里所说的
字符操作包括
(1)删除一个字符;
(2)插入一个字符;
(3)将一个字符改为另一个字符。
将字符串A变换为字符串B 所用的最少字符操作数称为字符串A到B 的编辑距离,记为
d(A,B)。试设计一个有效算法,对任给的2 个字符串A和B,计算出它们的编辑距离d(A,B)。
编程任务:
对于给定的字符串A和字符串B,编程计算其编辑距离d(A,B)。
输入
第一行是字符串A,第二行是字符串B。
输出
输出一行,即将编辑距离d(A,B)输出。
样例输入
fxpimu
xwrs
样例输出
5
提示
两个字符串长度均不超过2000

solution

就是普通的 d p dp dp
d p [ i ] [ j ] dp[i][j] dp[i][j]为用最少的字符操作将字符串A的前i位 转换为字符串B的前j位
操作1: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + 1 dp[i][j]=dp[i-1][j]+1 dp[i][j]=dp[i1][j]+1
操作2: d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + 1 dp[i][j]=dp[i][j-1]+1 dp[i][j]=dp[i][j1]+1
操作3: d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + ( a [ i ] ! = b [ j ] ) dp[i][j]=dp[i-1][j-1]+(a[i]!=b[j]) dp[i][j]=dp[i1][j1]+(a[i]!=b[j])
最后取个 m i n min min就是 d p [ i ] [ j ] dp[i][j] dp[i][j]
上代码

#include<cstdio>
#include<cstring>
char a[2002],b[2002];
int la,lb,dp[2002][2002];
int minn(int a, int b){
    
    return a<b?a:b;}
int main()
{
    
    
	gets(a);gets(b);
	la=strlen(a);lb=strlen(b);
	for(int i=0;i<=la;i++)dp[i][0]=i;
	for(int i=0;i<=lb;i++)dp[0][i]=i;
	for(int i=1;i<=la;i++)
	{
    
    
		for(int j=1;j<=lb;j++)dp[i][j]=minn(minn(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+(a[i-1]!=b[j-1]));
	}
	printf("%d\n",dp[la][lb]);
}

[Usaco2004 Nov]Apple Catching 接苹果

fzoj2321
题目描述
很少有人知道奶牛爱吃苹果。农夫约翰的农场上有两棵苹果树(编号为1和2),每一棵树上都长满了苹果。奶牛贝茜无法摘下树上的苹果,所以她只能等待苹果从树上落下。但是,由于苹果掉到地上会摔烂,贝茜必须在半空中接住苹果(没有人爱吃摔烂的苹果)。贝茜吃东西很快,所以她接到苹果后仅用几秒钟就能吃完。
每一分钟,两棵苹果树其中的一棵会掉落一个苹果。贝茜已经过了足够的训练,只要站在树下就一定能接住这棵树上掉落的苹果。同时,贝茜能够在两棵树之间快速移动(移动时间远少于1分钟),因此当苹果掉落时,她必定站在两棵树其中的一棵下面。此外,奶牛不愿意不停地往返于两棵树之间,因此会错过一些苹果。
苹果每分钟掉落一个,共T(1<=T<=1000)分钟,贝茜最多愿意移动W(1<=W<=30)次。现给出每分钟掉落苹果的树的编号,要求判定贝茜能够接住的最多苹果数。开始时贝茜在1号树下。
输入
第1行:由空格隔开的两个整数:T和W;
第2…T+1行:1或2(每分钟掉落苹果的树的编号)。
输出
一行一个数,表示在贝茜移动次数不超过W的前提下她能接到的最多苹果数。
样例输入
7 2
2
1
1
2
2
1
1
样例输出
6
提示
输入说明:7分钟内共掉落7个苹果——第1个从第2棵树上掉落,接下来的2个苹果从第1棵树上掉落,再接下来的2个从第2棵树上掉落,最后2个从第1棵树上掉落。
输出说明:贝茜不移动直到接到从第1棵树上掉落的两个苹果,然后移动到第2棵树下,直到接到从第2棵树上掉落的两个苹果,最后移动到第1棵树下,接住最后两个从第1棵树上掉落的苹果。这样贝茜共接住6个苹果。

solution

d p [ i ] [ j ] dp[i][j] dp[i][j]表示贝茜在时间i时站在树j下
那么 d p [ i ] [ j ] dp[i][j] dp[i][j]有两种情况 一种是上一分钟在 j j j树下 另一种是上一分钟在 3 − j 3-j 3j树下
所以
d p [ i ] [ j ] = ( a [ i ] = j % &ThinSpace; 2 + 1 ) + m a x { d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − 1 ] } dp[i][j]=(a[i]=j\%\,2+1)+max\{dp[i-1][j],dp[i-1][j-1]\} dp[i][j]=(a[i]=j%2+1)+max{ dp[i1][j],dp[i1][j1]}

#include<cstdio>
int t,w,x,ans,a[1001],dp[1001][31];
int maxx(int a, int b){
    
    return a>b?a:b;}
int main()
{
    
    
	scanf("%d%d",&t,&w);
	for(int i=1;i<=t;i++)scanf("%d",&a[i]);
	for(int i=1;i<=t;i++)dp[i][0]=dp[i-1][0]+2-a[i];
	for(int i=1;i<=t;i++)
	{
    
    
		for(int j=1;j<=w&&j<=i;j++)
		{
    
    
			if(j==0)
			{
    
    
				if(a[i]==1)dp[i][j]++;
				continue;
			}
			dp[i][j]=(a[i]==(j%2+1))+maxx(dp[i-1][j],dp[i-1][j-1]);
		}
	}
	for(int i=0;i<=w;i++)ans=maxx(ans,dp[t][i]);
	printf("%d\n",ans);
}

NOIP2014 飞扬的小鸟

fzoj1650
题目描述
在这里插入图片描述
输入
第 1 行有 3 个整数 n,m,k,分别表示游戏界面的长度,高度和水管的数量,每两个整数之间用一个空格隔开;

接下来的 n 行,每行 2 个用一个空格隔开的整数 X 和 Y,依次表示在横坐标位置 0~n-1上玩家点击屏幕后,小鸟在下一位置上升的高度 X,以及在这个位置上玩家不点击屏幕时,小鸟在下一位置下降的高度 Y。

接下来 k 行,每行 3 个整数 P,L,H,每两个整数之间用一个空格隔开。每行表示一个管道,其中 P 表示管道的横坐标,L 表示此管道缝隙的下边沿高度为 L,H 表示管道缝隙上边沿的高度(输入数据保证 P 各不相同,但不保证按照大小顺序给出)。
输出
第一行,包含一个整数,如果可以成功完成游戏,则输出 1,否则输出 0。

第二行,包含一个整数,如果第一行为 1,则输出成功完成游戏需要最少点击屏幕数,否则,输出小鸟最多可以通过多少个管道缝隙。
样例输入
10 10 6
3 9
9 9
1 2
1 3
1 2
1 1
2 1
2 1
1 6
2 2
1 2 7
5 1 5
6 3 5
7 5 8
8 7 9
9 1 3
样例输出
1
6
提示

solution

首先是初始化 没有柱子的地方默认柱子下端是 0 0 0上端是 m + 1 m+1 m+1
然后来到了跳跃的问题 每次跳可以向上跳和向下掉 但只能跳和掉二选一 跳能一直跳掉只能掉一次
这…这不是混合背包吗![震惊] 而且向上是完全背包 向下是 0 − 1 0-1 01背包
所以向下的方程是 d p [ i ] [ j ] = m i n { d p [ i ] [ j ] , d p [ i − 1 ] [ j − y [ i − 1 ] ] } dp[i][j]=min\{dp[i][j],dp[i-1][j-y[i-1]]\} dp[i][j]=min{ dp[i][j],dp[i1][jy[i1]]}
向上的方程是 d p [ i ] [ j ] = m i n { d p [ i − 1 ] [ j − k x [ i − 1 ] ] + k ] } , k ∈ [ 1 , ⌊ j x [ i − 1 ] ⌋ ] dp[i][j]=min\{dp[i-1][j-kx[i-1]]+k]\} ,k\in[1,\left \lfloor \frac{j} {x[i-1]} \right \rfloor] dp[i][j]=min{ dp[i1][jkx[i1]]+k]}k[1,x[i1]j]…①
这时取 d p [ i ] [ j − x [ i − 1 ] dp[i][j-x[i-1] dp[i][jx[i1]
d p [ i ] [ j − x [ i − 1 ] ] = m i n { d p [ i − 1 ] [ j − ( k − 1 ) x [ i − 1 ] + ( k − 1 ) } , k ∈ [ 1 , ⌊ j − x [ i − 1 ] x [ i − 1 ] ⌋ ] dp[i][j-x[i-1]]=min\{dp[i-1][j-(k-1)x[i-1]+(k-1)\},k\in[1,\left \lfloor \frac{j-x[i-1]} {x[i-1]} \right \rfloor] dp[i][jx[i1]]=min{ dp[i1][j(k1)x[i1]+(k1)}k[1,x[i1]jx[i1]]…②
由①式 − - ②式得 d p [ i ] [ j ] = d p [ i ] [ j − x [ i − 1 ] ] + 1 , k ≥ 2 dp[i][j]=dp[i][j-x[i-1]]+1,k\ge2 dp[i][j]=dp[i][jx[i1]]+1k2 ( ( ( k k k 是否大于2好像已经没关系了… ) ) )
另外需要注意的细节:
1.不要忘了把 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]的初始值也设为inf
2.输入数组 x , y x,y x,y是从 0 0 0 n − 1 n-1 n1 而初始化管子是从 1 1 1 n n n的 第一次就是这么错的…
3.每次算完 d p [ i ] [ 0... m ] dp[i][0...m] dp[i][0...m]后 要遍历一遍把管子覆盖的区域的 d p dp dp值都设为inf
O ( m ) O(m) O(m)处理初始化 O ( n m ) O(nm) O(nm) d p dp dp 总复杂度 O ( n m ) O(nm) O(nm)没问题
细节见代码

扫描二维码关注公众号,回复: 12011590 查看本文章
#include<cstdio>
inline void read(int &x)
{
    
    
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
    
    if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){
    
    s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
	x=s*w;
}
int minn(int a, int b){
    
    return a<b?a:b;}
const int inf=0x7ffffff;
int n,m,k,now,ans,pipx,x[10001],y[10001],pipedown[10001],pipeup[10001],dp[10001][1001];
int main()
{
    
    
	read(n);read(m);read(k);
	for(int i=0;i<n;i++)read(x[i]),read(y[i]);
	for(int i=1;i<=n;i++)pipeup[i]=m+1,pipedown[i]=0;
	for(int i=1;i<=k;i++)
	{
    
    
		read(pipx);
		read(pipedown[pipx]);read(pipeup[pipx]);
	}
	for(int i=1;i<=n;i++)for(int j=0;j<=m;j++)dp[i][j]=inf;
	dp[0][0]=inf;
	for(int i=1;i<=n;i++)
	{
    
    
		for(int j=1;j<=m;j++)
		{
    
    
			if(j>=x[i-1])dp[i][j]=minn(minn(dp[i][j],dp[i-1][j-x[i-1]]+1),dp[i][j-x[i-1]]+1);
			if(j==m)for(int k=j-x[i-1];k<=m;k++)dp[i][j]=minn(minn(dp[i][j],dp[i-1][k]+1),dp[i][k]+1);
		}
		for(int j=pipedown[i]+1;j<=pipeup[i]-1;j++)if(j+y[i-1]<=m)dp[i][j]=minn(dp[i][j],dp[i-1][j+y[i-1]]);
		for(int j=1;j<=pipedown[i];j++)dp[i][j]=inf;
		for(int j=pipeup[i];j<=m;j++)dp[i][j]=inf;
	}
	now=k,ans=inf;
	for(int i=n;i;i--)
	{
    
    
		for(int j=pipedown[i]+1;j<=pipeup[i]-1;j++)if(dp[i][j]<inf)ans=minn(ans,dp[i][j]);
		if(ans!=inf)break;
		if(pipeup[i]<=m)now--;
	}
	if(now==k)printf("1\n%d\n",ans);
	else printf("0\n%d\n",now);
}

虽然是提高组 D a y 1 T 3 Day1T3 Day1T3不过只要理清思路打出代码其实也不难

动态规划 贝茜的晨练计划

fzoj1313
题目描述
奶牛们打算通过锻炼来培养自己的运动细胞,作为其中的一员,贝茜选择的运动方式是每天进行N(1 <= N <= 10,000)分钟的晨跑。在每分钟的开始,贝茜会选择下一分钟是用来跑步还是休息。
贝茜的体力限制了她跑步的距离。更具体地,如果贝茜选择在第i分钟内跑步,她可以在这一分钟内跑D_i(1 <= D_i <= 1,000)米,并且她的疲劳度会增加1。不过,无论何时贝茜的疲劳度都不能超过M(1 <= M <= 500)。如果贝茜选择休息,那么她的疲劳度就会每分钟减少1,但她必须休息到疲劳度恢复到0为止。在疲劳度为0时休息的话,疲劳度不会再变动。晨跑开始时,贝茜的疲劳度为0。
还有,在N分钟的锻炼结束时,贝茜的疲劳度也必须恢复到0,否则她将没有足够的精力来对付这一整天中剩下的事情。
请你计算一下,贝茜最多能跑多少米。
输入
第1行: 2个用空格隔开的整数:N 和 M
第2…N+1行: 第i+1为1个整数:D_i
输出
输出1个整数,表示在满足所有限制条件的情况下,贝茜能跑的最大距离
样例输入
5 2
5
3
4
2
10
样例输出
9
提示
贝茜在第1分钟内选择跑步(跑了5米),在第2分钟内休息,在第3分钟内跑步(跑了4米),剩余的时间都用来休息。因为在晨跑结束时贝茜的疲劳度必须为0,所以她不能在第5分钟内选择跑步

solution

d p [ i ] [ j ] dp[i][j] dp[i][j]为贝茜在 i i i分钟用了 j j j体力时跑过的最远距离
那么分三种情况
1.下一秒要接着跑: d p [ i + 1 ] [ j + 1 ] = m a x { d p [ i + 1 ] [ j + 1 ] , d p [ i ] [ j ] + d [ i + 1 ] } dp[i+1][j+1]=max\{dp[i+1][j+1],dp[i][j]+d[i+1]\} dp[i+1][j+1]=max{ dp[i+1][j+1],dp[i][j]+d[i+1]}
2.从下一秒开始休息: d p [ i + j ] [ 0 ] = m a x { d p [ i + j ] [ 0 ] , d p [ i ] [ j ] } dp[i+j][0]=max\{dp[i+j][0],dp[i][j]\} dp[i+j][0]=max{ dp[i+j][0],dp[i][j]}
3.当 j = 0 j=0 j=0时 还可以接着休息 d p [ i + 1 ] [ 0 ] = m a x { d p [ i + 1 ] [ 0 ] , d p [ i ] [ 0 ] } dp[i+1][0]=max\{dp[i+1][0],dp[i][0]\} dp[i+1][0]=max{ dp[i+1][0],dp[i][0]}

#include<cstdio> 
int n,m,dp[10010][510],d[10010]; 
int maxx(int a, int b){
    
    return a>b?a:b;} 
int main() 
{
    
     
    scanf("%d%d",&n,&m); 
    for(int i=1;i<=n;i++)scanf("%d",&d[i]); 
    dp[1][0]=0,dp[1][1]=d[1]; 
    for(int i=1;i<=n;i++) 
    {
    
     
        for(int j=0;j<=m;j++) 
        {
    
     
            dp[i+1][j+1]=maxx(dp[i+1][j+1],dp[i][j]+d[i+1]); 
            if(i+j<=n)dp[i+j][0]=maxx(dp[i+j][0],dp[i][j]); 
            if(j==0&&i<n)dp[i+1][0]=maxx(dp[i+1][0],dp[i][0]); 
        } 
    } 
    printf("%d\n",dp[n][0]); 
}

01背包 采药2

fzoj1244
题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?

输入
第一行包含两个正整数N,M。M表示总共能够用来采药的时间,N代表山洞里的草药的数目。接下来的N行每行包括两个的整数,分别表示采摘某株草药的时间Ti和这株草药的价值Vi。
输出
包含一个整数表示规定时间内可以采到的草药的最大总价值。
样例输入

3 9
10 10
8 1
1 2

样例输出

3

提示
【数据规模和约定】
50%的数据中 N,M ≤ 1000;
100%的数据中 N,M ≤ 100000,Ti,Vi ≤10。

solution

显然直接01背包会超时
显然我们发现tivi都小于等于10
显然我们应该统计每一种药的数量
显然变成了多重背包
显然我们想出了正解
显然具体详细细节要看代码

#include<cstdio>
inline void read(int &x)
{
    
    
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
    
    if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){
    
    s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
	x=s*w;
}
int n,m,x,y,nx,ny,now,pnt,med[11][11],dp[100001];
int minn(int a, int b){
    
    return a<b?a:b;}
int maxx(int a, int b){
    
    return a>b?a:b;}
int main()
{
    
    
	read(n),read(m);
	for(int i=1;i<=n;i++)
	{
    
    
		read(x),read(y);
		med[x][y]++;
	}
	for(int i=0;i<=10;i++)
	{
    
    
		for(int j=0;j<=10;j++)
		{
    
    
			x=med[i][j],y=1;
			while(x)
			{
    
    
				now=minn(x,y);
				nx=now*i;ny=now*j;
				for(int k=m;k>=nx;k--)dp[k]=maxx(dp[k],dp[k-nx]+ny);
				x-=now;
				y<<=1;
			}
		}
	}
	printf("%d\n",dp[m]);
}

[NOI1995] 石子合并

fzoj2092
题目描述
设有N堆沙子排成一排,其编号为1,2,3,…,N(N<=300)。每堆沙子有一定的数量,可以用一个整数来描述,现在要将这N堆沙子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆沙子的数量之和,合并后与这两堆沙子相邻的沙子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同,如有4堆沙子分别为 1 3 5 2 我们可以先合并1、2堆,代价为4,得到4 5 2 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24,如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22;问题是:找出一种合理的方法,使总的代价最小。输出最小代价。
输入
第一行一个数N表示沙子的堆数N。
第二行N个数,表示每堆沙子的质量。 <=1000
输出
合并的最小代价
样例输入
4
1 3 5 2
样例输出
22

solution

这是一道区间dp题
我们用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示合并区间 [ i , j ] [i,j] [i,j]所需要的最小代价
那么很容易得出关系式 d p [ i ] [ j ] = m i n { d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ k + 1 ] [ j ] + s [ j ] − s [ i − 1 ] } dp[i][j]=min\{dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]\} dp[i][j]=min{ dp[i][j],dp[i][k]+dp[k+1][j]+s[j]s[i1]}

#include<cstdio> 
int n,a[301],s[301],dp[301][301]; 
int minn(int a, int b){
    
    return a<b?a:b;} 
int main() 
{
    
     
    scanf("%d",&n); 
    for(int i=1;i<=n;i++) 
    {
    
     
        scanf("%d",&a[i]); 
        s[i]=s[i-1]+a[i]; 
    } 
    for(int l=1;l<n;l++) 
    {
    
     
        for(int i=1,j=l+1;j<=n;i++,j++) 
        {
    
     
            dp[i][j]=3000001; 
            for(int k=i;k<j;k++)dp[i][j]=minn(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]); 
        } 
    } 
    printf("%d\n",dp[1][n]); 
}

矩阵

fzoj3641
题目描述
矩阵乘法是定义在矩阵上的二元运算,支持结合律但不支持交换律。一个m*n的矩阵A和一个n*p的矩阵B相乘,需要进行m*n*p次乘法运算,并得到一个m*p的矩阵。
现给出N个矩阵相乘,求进行乘法运算的最小次数。
输入
第一行一个正整数N,表示矩阵的个数。
接下来N行每行两个数m[i]和n[i],表示这个矩阵的行数和列数。
数据保证m[i]=n[i-1],即矩阵可以相乘。
输出
输出一行一个正整数,表示乘法运算的最小次数。
样例输入
3
50 10
10 20
20 5
样例输出
3500
提示
运算顺序为A*(B*C),计算B*C需要10*20*5=1000次乘法,然后得到一个10*5的矩阵BC去和A相乘,乘法次数为50*10*5=2500,总乘法次数为3500.

对于30%的数据,N<=10
对于60%的数据,N<=100
对于100%的数据,2<=N<=500,1<=n[i],m[i]<=50

solution

又是一道区间 d p dp dp d p [ i ] [ j ] dp[i][j] dp[i][j]为第 i i i个矩阵到第 j j j个所用的乘法次数最小值
那么 d p [ i ] [ j ] = m i n { d p [ i ] [ k ] + d p [ k + 1 ] [ j ] + p [ i ] [ 0 ] ∗ p [ k ] [ 1 ] ∗ p [ j ] [ 1 ] , k ∈ [ i , j ] } dp[i][j]=min\{dp[i][k]+dp[k+1][j]+p[i][0]*p[k][1]*p[j][1],k\in[i,j]\} dp[i][j]=min{ dp[i][k]+dp[k+1][j]+p[i][0]p[k][1]p[j][1]k[i,j]}
上代码

#include<cstdio>
int minn(int a, int b){
    
    return a<b?a:b;}
int n,p[501][2],dp[501][501];
int main()
{
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&p[i][0],&p[i][1]);
	for(int l=2;l<=n;l++)
	{
    
    
		for(int i=1,j=l;j<=n;i++,j++)
		{
    
    
			dp[i][j]=2000000000;
			for(int k=i;k<=j;k++)
			{
    
    
				dp[i][j]=minn(dp[i][j],dp[i][k]+dp[k+1][j]+p[i][0]*p[k][1]*p[j][1]);
			}
		}
	}
	printf("%d\n",dp[1][n]);
}

这就是今天的例题 如果有问题可以发到评论区或者加 Q Q 407694747 QQ 407694747 QQ407694747我们一起讨论
各位大佬各路神犇请多指教

猜你喜欢

转载自blog.csdn.net/dhdhdhx/article/details/100155245
今日推荐