21.4.11周末总结(第四五周)

最近好像有点崩溃,干啥啥不行,做啥啥不会,我可能需要一个垃圾桶🗑️

动态规划

实话说,这一类的题好难想方法,半天甚至一两天才能做一道题,一直要换思路,换想法,现在大多做的题都是参考的别人的,理解题目和代码都是一大难点
之前学的贪心算法是以小见大,以局部最优解来达到整体最优解的目的,找到一个规律就可以解整个题。而动态规划也是局部最优解,但是它是不断变化的,会跟你上一次做出的选择有关。
它最大的特点是会记录你之前已经计算过的子问题的解,下次用到的时候直接使用,然后在此基础上再继续判断,而不是每次都枚举新的一遍,可以节省很多时间。
动态规划一般是根据空间顺序和时间顺序来划分。
一般解题步骤:
1.判断问题是否具有最优子结构性质
2.把问题划分为若干个子问题(分阶段)(确定状态)
3.写公式,状态转移方程
4.初始条件和边界条件
5.递推求解(计算顺序)

大致分类:
根据我当前做过的题来看,可以分成几类,其实大多都是衍生题,就是原来的题型,然后多一些限制条件罢了。
一、一维数组
1.C(FatMouse’s Speed)
将问题转化之后,就变成了一个数列求最长上升子序列问题,但是要输出他们相对应的原本的位置,这就要用另一个数组标记,但是是两个一维的,他们俩没什么联系。
2.D(Tickets)
有两种卖票方法,可以单张卖,也可以两张一起卖,两种方法的花费的时间不同,问最后售卖时间总和最短是多少。虽然有两种情况,但是用一维的标记当前时间,分别比较下次两张卖还是分开卖的时间即可。

二、二维数组
给出两个限制条件的就要用一个二维的数组来标记这两类。
1.字符串比较

for(int i=1; i<=l1; i++)
        {
            for(int j=1;j<=l2;j++)
            {
                if(a[i-1]==b[j-1])   dp[i][j]=dp[i-1][j-1]+1;
                else  dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        }
        cout << dp[l1][l2]<< endl;

(1).Q(Common Subsequence)
比较两个字符串最多有几个连续的相同的字符
(2).(反恐训练营)
其实就是两个字符串求连续的最大相同的个数,但是同时要输出他们相对应的值。
2.
(1).A(Jumping Cows)
有一种药品,可以给你能量,但是奇数次选择是增加,偶数次选择是减少,就要用二维数组来分别标记奇数和偶数的情况。

三、最大子段和(连续)
这类问题没有统一写法,不同的题目要求差别还挺大的
1.P(Max Sum) 最大子段和,只选一段

2.最大M段子段和

3.T( 最大连续子序列)
输出该序列的第一个和最后一个元素

四、最大子序列(不连续)

memset(b,0,sizeof(b));
b[1]=a[1];
for(int i=2;i<=n;i++)        
{            
        for(int j=i-1;j>=1;j--)           
      {                
             if(a[i]>a[j])   b[i]=max(b[i],b[j]+1);           
       }       
 }        
 sort(b+1,b+1+n);        
 cout << b[n] << endl;

1.J(Dynamic Programming)
求绝对值和最大的子序列,比较方式
2.T( 最大连续子序列)
输出该序列的第一个和最后一个元素
3.U(Super Jumping!)
求最大严格上升子序列的和

解题小方法
1.自底而上(逆序解法)
①数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
题目要求虽然是从顶部出发,但是从底部的最后一个元素开始判断显然更简单
②记忆化搜索,目前就做过一个题,用了递归的思想,虽然是从左上角开始的,但是左上角跟右边和下边有关,就要计算他们的值,一直推,最后需要的其实是右下角的值,也就是相当于倒着来

int dfs(int x,int y)//这是一个递归函数
{
    if(dp[x][y]>=0) return dp[x][y]%mod;//之前计算过的就跳过
    dp[x][y]=0;
    for(int i=0;i<=a[x][y];i++)
    {
        for(int j=0;j<=a[x][y]-i;j++)
        {
            if(x+i>=1&&x+i<=n&&y+j<=m&&y+j>=1) dp[x][y]=(dp[x][y]+dfs(x+i,y+j))%mod;
        }
    }
    return dp[x][y]%mod;
}

2.自顶而下
大部分题都是从前面开始的,没啥好说的

小知识
1.转换公式的方式,比如,幂函数换成取对数,有log函数,比pow准确
2.register 寄存器,用来声明变量,只能用于少量的,可以加快速度
3.要用两列数组交换使用,可以用异或,和1取值,就可以0和1之间交换
4.求中心对称图形方法:
①n为偶数
②对角线中心重合
5.求或运算,直接用符号"|"

博弈

博弈题就是两个人比赛的问题,这种题大多是题干简练,写的代码也简练,重点在思想上,只要找到其中的规律就很好写。
重点是他们的必败点,谁遇到了谁输。
棋盘类型:
给一个n*m的棋盘,A在左上角先下,然后B在右下角,只能走上次相邻的格子,二者轮流下,谁无路可走谁就失败
棋盘问题通常是与奇偶数有关,仔细观察其中的联系即可

#include<iostream>
using namespace std;

int main()
{
    int n;
    while(cin >> n&&n)
    {
        if(n%2==0) cout << "8600" << endl;
        else cout << "ailyanlu" << endl;
    }
    
    return 0;
}

巴什博弈:
(1)、原型只有一堆n个物品,两个人轮流从中取物,规定每次最少取一个,最多取m个,最后取光者为胜。当n=m+1时,由于一次最多只能取m个,所以无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜,所以当一方面对的局势是n%(m+1)=0时,其面临的是必败的局势。
(2)变式(左边也有范围限制)每行有三个数字n,p,q,表示一堆硬币一共有n枚,从这个硬币堆里取硬币,一次最少取p枚,最多q枚,如果剩下少于p枚就要一次取完。两人轮流取,直到堆里的硬币取完,最后一次取硬币的算输。

#include<iostream>
using namespace std;
int main()
{
    int m,n,p,q;
    while(cin >> n>>p>>q)
    {
          m=n%(p+q);//原型就是(1+m),变式是(p+q)
          if(m==0) cout << "先手胜" << endl;
          else if(m<=p) cout << "先手败" << endl;
          else cout << "先手胜" << endl;
    }
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值