寒假笔记·记忆化搜索

记忆化搜索

例题:P2858 [USACO06FEB]奶牛零食Treats for the Cows

原题地址

题目描述

FJ has purchased N (1 <= N <= 2000) yummy treats for the cows who get money for giving vast amounts of milk. FJ sells one treat per day and wants to maximize the money he receives over a given period time.

The treats are interesting for many reasons:The treats are numbered 1…N and stored sequentially in single file in a long box that is open at both ends. On any day, FJ can retrieve one treat from either end of his stash of treats.Like fine wines and delicious cheeses, the treats improve with age and command greater prices.The treats are not uniform: some are better and have higher intrinsic value. Treat i has value v(i) (1 <= v(i) <= 1000).Cows pay more for treats that have aged longer: a cow will pay v(i)*a for a treat of age a.Given the values v(i) of each of the treats lined up in order of the index i in their box, what is the greatest value FJ can receive for them if he orders their sale optimally?

The first treat is sold on day 1 and has age a=1. Each subsequent day increases the age by 1.
输入输出格式

输入格式:
Line 1: A single integer, N

Lines 2…N+1: Line i+1 contains the value of treat v(i)

输出格式:
Line 1: The maximum revenue FJ can achieve by selling the treats

输入输出样例

输入样例#1:
5
1
3
1
5
2
输出样例#1:
43
说明

Explanation of the sample:

Five treats. On the first day FJ can sell either treat #1 (value 1) or treat #5 (value 2).

FJ sells the treats (values 1, 3, 1, 5, 2) in the following order of indices: 1, 5, 2, 3, 4, making 1x1 + 2x2 + 3x3 + 4x1 + 5x5 = 43.

代码:
区间dp

1、 普通的搜索54分。
每一次的抉择是取当前区间的左边还是右边,搜索的边界条件是取到最后只剩下一个元素

2、记忆化搜索 我们发现之前取的抉择可能不完全一样,但是现在面临同样的状态,那么我们就不需要搜索多遍,开一个f[l][r]数组,表示区间[l,r]能提供的最大价值,边界条件为取完时,即数列为空时。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,ans,v[2020],f[2020][2020];
int dfs(int ste,int l,int r)
{
    if(r<l)return 0;
    if(f[l][r])return f[l][r];
    f[l][r]=max(dfs(ste+1,l+1,r)+ste*v[l],dfs(ste+1,l,r-1)+ste*v[r]);
    return f[l][r];
}
int main()
{
    scanf("%d",&n);
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++) scanf("%d",&v[i]);
    dfs(1,1,n);
    printf("%d\n",f[1][n]);
    return 0;
}

例题:P2196 挖地雷

原题地址

题目描述

在一个地图上有N个地窖(N≤20),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。

输入输出格式

输入格式:
有若干行。

第1行只有一个数字,表示地窖的个数N。

第2行有N个数,分别表示每个地窖中的地雷个数。

第3行至第N+1行表示地窖之间的连接情况:

第3行有n-1个数(0或1),表示第一个地窖至第2个、第3个、…、第n个地窖有否路径连接。如第3行为1 1 0 0 0 … 0,则表示第1个地窖至第2个地窖有路径,至第3个地窖有路径,至第4个地窖、第5个、…、第n个地窖没有路径。

第4行有n-2个数,表示第二个地窖至第3个、第4个、…、第n个地窖有否路径连接。

… …

第n+1行有1个数,表示第n-1个地窖至第n个地窖有否路径连接。(为0表示没有路径,为1表示有路径)。

输出格式:
有两行

第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。

第二行只有一个数,表示能挖到的最多地雷数。

输入输出样例

输入样例#1:
5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1
输出样例#1:
1 3 4 5
27

代码:
错误代码:

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,qq[30],ss[30][30],vis[30],tt[30];
int dfs(int x,int t,int k)
{
	int i,q,u=k;
	for(i=x+1;i<=n;i++)
	{
		if(ss[x][i]&&!vis[i])
		{
			vis[i]=1;
			q=dfs(i,t+1,u+qq[i]);
			if(k<q)
			{
				tt[t]=i;
				k=q;
			}
			vis[i]=0;
		}
	}
	return k;
}
int main()
{
    int i,j,ans=0,p;
	scanf("%d",&n);
	memset(ss,0,sizeof(ss));
	memset(vis,0,sizeof(vis));
	memset(tt,0,sizeof(tt));
	for(i=1;i<=n;i++)
		scanf("%d",&qq[i]);
	for(i=1;i<n;i++)
		for(j=i+1;j<=n;j++)
		{scanf("%d",&ss[i][j]);}
	for(i=1;i<=n;i++)
	{
		vis[i]=1;
		p=dfs(i,2,qq[i]);
		if(ans<p)
		{
			tt[1]=i;
			ans=p;
		}
		vis[i]=0;
	}
	for(i=1;tt[i+1];i++)
		printf("%d ",tt[i]);
	printf("%d\n",tt[i]);
	printf("%d\n",ans);
    return 0;
}

错误分析:
每次在从低深度向高深度重新搜索的时候,k不变,使动态数组tt[]中低深度的数重复更新,样例结果输出:
1 5 5 5
27

正确代码:
d[i]:从i出发能获得的最多的地雷数量

next[i]:相应的路径

w[i]:i结点原有的地雷数量

count(u):
对于从u出发能到达的每一个与u相邻的结点i
{
if (d[i]未计算)d[i]=count(i);
记录v=max{d[i]},k为v的对应的结点
}
next[u]=k;
return v+w[u];
主算法

对于每一个入度为0的点z
d[z]=count(z);
记录y=max{d[z]},x为对应的结点
while(x!=-1)
输出x
x=next[x]
输出y

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int n,a,y,x;
int d[30],w[30],id[30],net[30];
bool linker[30][30];

int count(int u)
{
    if(d[u])return d[u];
    int v=0,k=-1;
    for(int i=1;i<=n;i++)
        if(linker[u][i])
        {
            if(d[i]==0)d[i]=count(i);
            if(d[i]>v)v=d[i],k=i;
        }
    net[u]=k;
    return v+w[u];
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>w[i];
        id[i]=0;
        net[i]=-1;
    }
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
        {
            cin>>a;
            if(a)linker[i][j]=true,id[j]++;
        }
    for(int i=1;i<=n;i++)
        if(id[i]==0)
        {
            d[i]=count(i);
            if(d[i]>y)y=d[i],x=i;
        }
    while(x!=-1)
    {
        cout<<x<<' ';
        x=net[x];
    }
    cout<<endl<<y<<endl;
    return 0;
}

与错误代码相比,增加d[i]存储从i出发所可以得到的地雷。

隐藏的记忆化搜索

例题:P2758 编辑距离

原题地址

题目描述

设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有三种:

1、删除一个字符;

2、插入一个字符;

3、将一个字符改为另一个字符;

!皆为小写字母!

输入输出格式

输入格式:
第一行为字符串A;第二行为字符串B;字符串A和B的长度均小于2000。

输出格式:
只有一个正整数,为最少字符操作次数。

输入输出样例

输入样例#1:
sfdqxbw
gfdgw
输出样例#1:
4

代码:
用在这一题当中我的思路如下:

1.确定子问题:

由于对于字符串的操作只有4种情况(删除,添加、更改、不变),所以该题的子问题就是进行了这4种操作后的A字符串变为B字符串需要多少步。

2.定义状态:

也就是说递归的dp函数需要哪些参数,参数越少越好因为需要建memo。后来想到dp(i,j)代表字符串A的前i个字符(包括第i个)变为字符串B的前j个(包括第j个)需要多少步。也就是说解出来dp(lenA,lenB)就可以了。

3.转移方程:

删:dp(i-1,j)+1 //字符串A的前i-1个字符变为字符串B的前j个需要多少步 【把字符串的第i个字符(最后一个)删除了】,删除需要一步因此加1

添:dp(i,j-1)+1 //将B[j]字符加在A字符串的最后面即添加,同样可以理解为将B[j]字符删掉(因为不用再考虑了)。

//字符串A的前i个字符变为字符串B的前j-1个需要多少步 添加需要一步因此加1

替:dp(i-1,j-1)+1 //字符串A和B的最后两个都相等了,因此都不用再考虑

//字符串A的前i-1个字符变为字符串B的前j-1个需要多少步 添加需要一步因此加1

不变:dp(i-1,j-1)//字符串A和B的最后两个都相等,不考虑。感性的说这种情况是理想情况。

4.避免重复求解

这个最简单,建个数组就行。

以下为AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char a[2020],b[2020];
int dp[2020][2020];
int kp(int u,int v)
{
    int t=1;
    if(dp[u][v]!=-1) return dp[u][v];
    if(u==0) return dp[u][v]=v;
    if(v==0) return dp[u][v]=u;
    if(a[u]==b[v]) t=0;
    return dp[u][v]=min(min(kp(u-1,v)+1,kp(u,v-1)+1),kp(u-1,v-1)+t);
}
int main()
{
    int m,n;
    scanf("%s",a+1);
    scanf("%s",b+1);
    m=strlen(a+1);
    n=strlen(b+1);
    memset(dp,-1,sizeof(dp));
    kp(m,n);
    printf("%d\n",dp[m][n]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43918350/article/details/87972015