4.11 周练 dp和搜索选集

1.P2758 编辑距离 线性dp

 这题主要难点是这三个步骤如何转换为状态转移。首先我们先确定状态,因为有两个字符串要进行匹配,首先会想到最长公共子序列问题,因为一个主串a要匹配成b,那么a的每一个子串都要与b的每一个子串匹配。

所以设dp[j][i]表示长度为j的a串要变成长度i的b串需要的最少步骤。也就是说此时的a子串完全等于此时的b子串

再来分析每个操作:

首先我们可以不进行操作,但是不操作又要保证a与b串匹配,只能当前a的字符与b的字符相同。才能匹配。所以当a[j]==a[i]时即可不进行操作,此时的操作数继承之前的 dp[j-1][i-1]

1.删除一个字符,首先,如果a串当前字符和b串当前字符相同,就没必要执行这一步。如果要删显然必须删除当前不同的a串字符。删除了当前字符,那么此时的状态则有删除之后的子串j-1得到。此时则需要计算dp[j-1][i]

2.插入一个字符,如果要在不匹配出插入一个字符,首先此字符肯定是与b串当前位置相同的字符。那么相当于a串的匹配不需要考虑b的这个字符,因为我能直接添加一个与他匹配,转而计算dp[j][i-1]。

3.替换一个字符,这个操作相对好理解一些,a串当前位置的字符不匹配,则替换该字符为b的字符达到匹配的效果。此时,a和b都不再需要考虑这个字符了,因为我能进行一次操作完成这个字符的匹配,所以转而计算dp[j-1][i-1].

所以状态转移方程为:

if(a[j]==b[i])

dp[j][i]=dp[j-1][i-1]

else

dp[j][i]=min(dp[j-1][i],dp[j][i-1],dp[j-1][i-1])+1

 最后,这题的最坑的地方,就是a串和b串的大小关系不确定,可能执行删除或添加的时候a的总长度已经超过b或者远小于b了,此时就要不断执行添加或删除操作。

而且使用记忆化搜索的时候要注意边界条件,因为数组的下标不能为负数,所以讨论的时候串的总长要+1,当前字符的下标其实为j-1和i-1

   for(int i = 1 ; i <= a.length() ; i++ ){
            dp[i][0] = i ;
        }
        for(int i = 1 ; i <= b.length() ; i++ ){
            dp[0][i] = i ;
        }


import java.math.BigInteger;
import java.util.HashMap;
import java.util.Scanner;

public class Main {
    public static String a;
    public static String b;
    public static int[][] dp = new int[2001][2001];

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        a = in.next();
        b = in.next();
        for(int j=0;j<=a.length();j++){
            for(int i=0;i<=b.length();i++)
                dp[j][i]=0;
        }
        for(int i = 1 ; i <= a.length() ; i++ ){//边界情况
            dp[i][0] = i ;
        }
        for(int i = 1 ; i <= b.length() ; i++ ){
            dp[0][i] = i ;
        }
       for(int j=1;j<=a.length();j++){
           for(int i=1;i<=b.length();i++){

               if(a.charAt(j-1)==b.charAt(i-1)){
                   dp[j][i]=dp[j-1][i-1];
               }
               else{
                   dp[j][i]=Math.min(Math.min(dp[j-1][i-1]+1,dp[j-1][i]+1),dp[j][i-1]+1);
               }
           }
       }
        System.out.println(dp[a.length()][b.length()]);
    }
//记忆化搜索
    public static int dfs(int l1,int l2){

        if(dp[l1][l2]!=-1){
            return dp[l1][l2];
        }
        if(l1==0){
            return dp[l1][l2]=l2;
        }
        if(l2==0){
            return dp[l1][l2]=l1;
        }

        if(a.charAt(l1-1)==b.charAt(l2-1)){
            return dp[l1][l2]=dfs(l1-1,l2-1);
        }

        int n=0x7fffffff;
        for(int j=1;j<=3;j++){
            if(j==1){
                n=Math.min(dfs(l1-1,l2)+1,n);
            }
            else if(j==2){
                n=Math.min(dfs(l1,l2-1)+1,n);
            }
            else
                n=Math.min(dfs(l1-1,l2-1)+1,n);
        }
        dp[l1][l2]=n;
        return n;

    }
}

2.P1077 [NOIP2012 普及组] 摆花 线性dp/多重背包方案数计算

 思路比较好想,

设dp[j][i]为由j种花最多能摆i个的方案数

计算方案数,则找出所有子状态然后将解相加即可。

状态转移方程为:

dp[j][i]+=dp[j-k][i-1]


#include <iostream>
using namespace std;
int dp[101][101];
int mod = (1e6) + 7;
int n, m;
int arr[10000];
int dfs(int now, int dep) {
    if (now == m)
        return 1;
    if (now > m)
        return 0;
    if (dep > n)
        return 0;
    if (dp[now][dep] != 0) {
        return dp[now][dep];
    }
    int t = 0;
    for (int j = 0; j <= arr[dep]; j++) {
        t = (t + dfs(now + j, dep + 1)) % mod;
    }
    dp[now][dep] = (t % mod);
    return t;

}
int main()
{
    cin >> n >> m;
    dp[0][0] = 1;
    for (int j = 1; j <= n; j++) {
        cin >> arr[j];
    }
   
    for (int j = 0; j <= m; j++) {
        for (int i = 1; i <= n; i++) {
            for (int k = 0; k <= arr[i]; k++) {
                if (j >= k)
                    dp[j][i] = (dp[j][i] + dp[j - k][i - 1]) % (mod);
            }
        }
   }
    cout << dp[m][n];
}

3.P1451 求细胞数量 bfs/连通块

 这题老水了,连通块染色即可。但是学到了一些输入的技巧,c语言用的不多不知道scanf这么好用



#include <iostream>
#include<algorithm>
#include<unordered_map>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long

struct node {
	int x, y;
	
}num[1001];


int n, m;
int arr[101][101]={0};
int vis[101][101]={0};
int v1[] = { 1,-1,0,0 };
int v2[]={0, 0,1,-1};
int ans = 0;
void  bfs(int x, int y) {
	queue<node>q;
	q.push({ x,y });
	vis[x][y] = 1;
	while (!q.empty()) {
		node now = q.front();
		q.pop();
		for (int j = 0; j < 4; j++) {
			int nx = now.x + v1[j], ny = now.y + v2[j];
			if (arr[nx][ny] && vis[nx][ny] == 0) {
				q.push({ nx,ny });
				vis[nx][ny] = 1;
				
			}
		}
	}
	
}
int main()
{
	cin >> n >> m;
	for (int j = 1; j <= n; j++) {
		for (int i = 1; i <= m; i++) {
			scanf("%1d", &arr[j][i]);//无空格输入
		}
	}
	for (int j = 1; j <= n; j++) {
		for (int i = 1; i <= m; i++) {
			if (arr[j][i] != 0 && vis[j][i] == 0)
				bfs(j, i), ans++;
		}
	}
	cout << ans;

}

4.P3958 [NOIP2017 提高组] 奶酪 dfs出口处理

 dfs搜索每个可以走的洞(两球心距离小于2r),并标记,如果搜到了,就进行答案标记,因为只需要输出能不能到顶面,所以只要搜索到了一种情况就算结束,标记为1全部返回


#include <iostream>
#include<algorithm>
#include<unordered_map>
#include<cstring>
using namespace std;
#define ll long long
int t, n, h, r,ok=0;
struct node {
	ll x, y, z;
	
}num[1001];

int cmp(node a, node b) {
	return a.z < b.z;
}
long long dis(node a, node b) {
	return ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z));
}
int dp[10001];
void  dfs(node a,int dep) {

	if (a.z + r >= h) {//到达了可以直通顶面的球说明可以,标记为1

		ok = 1;
		return ;
	}
	if (ok == 1)//已经找过了全部返回
		return;
	for (int j = 0; j < n; j++) {
		
		if (dis(num[j], a) <= ((ll)4 * r*r)&& dp[j]==0) {//注意平方
			dp[j]=1;
			dfs(num[j],j);
		}
	}
}
int main()
{
	cin >> t;
	while (t--) {
		cin >> n >> h >> r;
		ok = 0;
		memset(dp, 0, sizeof(dp));
		memset(num, 0, sizeof(num));
		for (int j = 0; j < n; j++) {
			cin >> num[j].x >> num[j].y >> num[j].z;
		}
		sort(num, num + n, cmp);
		
		for (int j = 0; j < n; j++) {
			if (num[j].z <= r)dfs(num[j],j);
		}
	
		if (ok)cout << "Yes" << endl;
		else cout << "No" << endl;

	}
	
	
}

5.15. 三数之和 - 力扣(LeetCode) (leetcode-cn.com)​​​​​​双指针和操作去重

 

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
            vector<vector<int>>arr;
            arr.reserve(nums.size()>256?256:nums.size());//预先分配空间
            if(nums.size()<3)
            return arr;
            sort(nums.begin(),nums.end());
            if(nums[0]>0)
            return arr;
            for(int k=0;k<nums.size();k++){
               int i=k+1,j=nums.size()-1;//左右双指针
               if(k && nums[k]==nums[k-1])//去重,当前数讨论过,跳过
                    continue;
                while(i<j){
                    long long sum=nums[i]+nums[j]+nums[k];
                
                    if(sum>0){
                        --j;
                    }
                    else if(sum<0){
                        ++i;
                    }
                    else{
                      
                         arr.push_back(vector<int>{nums[k],nums[i],nums[j]});
                          ++i;
                          --j;
                        while(i<j&&nums[i]==nums[i-1])//去重
                        ++i;
                         
                        while(i<j&&nums[j]==nums[j+1])
                        --j;
             
                    }
                }
                
            }
            return arr;
    }
};

​​​​​​ 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值