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].
所以状态转移方程为:
最后,这题的最坑的地方,就是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个的方案数
计算方案数,则找出所有子状态然后将解相加即可。
状态转移方程为:
#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;
}
};