• ACM课程总结




    当我还是一个被P哥哥忽悠来的无知少年时,以为编程只有C语言那么点东西,半个学期学完C语言的我以为天下无敌了,谁知自从有了杭电练习题之后,才发现自己简直就是渣渣……咳咳进入正题:

    STL篇:

    成长为一名绝世高手,入门是必须的,呐,入门第一步,ACM程序设计书一本,里面记载了STL各种花式招数,实战之中方便快捷,实在是各武林人士入门必选~自此我等一行数十人踏上了课程练习这一不归路,此次训练惨烈程度异常,因为正值寒假所以各位师兄弟被过年这一障碍干扰,无法专心修炼,经过了半个寒假的闯关我终于通关,但是没想到还有下一层关卡在等着我们。

    模拟篇:

    模拟,特别是大模拟,在实战中是最为头疼的了,代码长,小数据多,但是这时候刚刚接触ACM的我并不知道这是模拟,起初我还以为这是给我们的水题,但是天真的我后来发现——我滴妈耶,这是个什么东西,模拟下棋,模拟打牌,模拟发牌,模拟生物实验,模拟……天哪出题人有这么无聊么,几百行代码,一不小心哪里错了又得一点点的调真是头疼,模拟最大的陷阱就是,题意很清楚,叫你非常想写,但是就是很麻烦让你又不很想写,纠结啊。但是平时练习嘛。

    从此立志开学前将这套题写完的我,开始了没日没夜的撸代码日子,但是一天一个题的速度实在是太慢了,直到开学也没能把这套题写完;

    贪心篇+二分三分:

    开学之后掌门人正式的开班收徒了,第一个专题就是贪心,另外辅助点的东西就是二分三分;

    贪心:

    贪心算法就跟名字一样解决问题一定要担心,拿田忌赛马这个例题来讲,他和大王赛马的原则就是不能吃亏,能赢的就要赢,赢不了就要用自己最次的马和你最好的马跑,浪费掉你最好的马。

    贪心的一般过程就是:

    (1)    将所给数据按照一定的顺序进行排序;

    (2)    从头开始进行处理问题;

    二分三分:

    二分可用于具有单调性的直线求零点,通过中间值与两段的值的比较不断地缩小范围就可以在一定的精度范围内求出解。一般的解体模板为:

    While(abs(left-right)>1e-6)

    {

             Mid=(left-right)/2;

    If(F(mid)>0)

        Left=mid;

    Else

        Right=mid;

    }

     

    三分可用于曲线求极值,一般模板为:

    while(fabs(left-right)>1e-6)//三分来判断最大的那个角度

    {

             mid=(left-right)/2;

             midmid=(mid+left)/2;

             if(F(mid)>F(midmid))

                      right=midmid;

             else

                      left=mid;

    }

    搜索篇:

    一入搜索深似海,不知何年是归期,^0^万能的搜索没有点空间想象能力学这个真费劲。

    这是一个心酸的故事,你可能不信,一开始我是拒绝的,5555555.没有什么问题是搜索解决不了的,如果有那就写两个搜索。学搜索开始的日子总是难熬的,广搜还好点,深搜就费劲了,递归没学明白,这个地方更是稀里糊涂的,看了一遍遍代码还是不明白,只能一遍遍的走程序,真是煎熬,深搜状态多,就拿联通块的问题来说,每一步都可能搜出来四步,很难弄的,光写就会写很多,好容易的把这个弄明白了,但是又迎来了又一大难题——超时!!!!!得想尽办法来剪枝,好烦啊!!!

    广搜:

     

    先将树顶的元素压入栈中,将他所有的儿子都搜出来压入栈中,这个搜索的元素就没有价值了,就可以将它出栈,接着只要栈不空就继续刚才的操作,直到栈空了,或者你找到你想要的解了。

    深搜

    深搜这里就不放图了(一个图得弄很长时间好麻烦滴说),就是一条路径找到头,直到尽头就返回搜索结果。不是我懒!!!我不懒!!!深搜我就理解了这么多,哼~

    记忆化搜索

    记忆化搜索和DP有些类似,用数组记录下来搜索的状态,每次搜索得到的结果和上一个状态进行比较取最优的结果,唯一有点不同的就是,DP一般是从后往前更新状态,但是记忆化搜索是从前往后更新状态。

     

     

     

     

    动态规划篇:

    上学期一次机缘巧合之下得知了,动态规划这一武林至高秘籍,但也只是皮毛。

    那是一个只有openjudge的日子,基础题做了几十道,就开始找找第二页有没有能做的题,采药!,这个题只有几行,就是一个最标准的01背包问题,之后……就没有之后了,这是什么鬼,问P哥哥这题怎么做,他说动态规划,还给我讲了怎么写,DP[i][j]表示第i个物品时,存放j大的物品的最优价值……等等,这都是什么鬼,这是什么,完全不明白他在说什么。回去看讲课视频动态规划,开始有那么一点懂了,但是接下来就有杭电练习题,就把这个放过去了。

    幸好我们有动态规划这个专题,这一下可找到好东西了,好多招式,01背包,多重背包,完全背包……(等等,怎么全是背包),不好意思先了解了解,还没看更多的东西。

    01背包:

    把这个过程理解下:在前i件物品放进容量v的背包时,

    它有两种情况:

    For(int i=1;i<=n;i++)

    For(int j=v;j>=c[i];j--)

    F[i][j]=max(F[i-1][v],F[i-1][j-c[i]]+w[i]);

    第一种是第i件不放进去,这时所得价值为:f[i-1][v]

    第二种是第i件放进去,这时所得价值为:f[i-1][v-c[i]]+w[i]

     

    这个图清晰明了,每次更新状态都是i行和i-1行作比较,谁的状态更优就取哪一个,最优解就是最后一个状态f[n][v];

    完全背包:

    完全背包:

    伪代码:

    for i=1..N

        for v=c[i]..V

            f[v]=max{f[v],f[v-c[i]]+w[i]}

    for(int j=c[i];j<=v;j++)

    max();

    j=c[i]+1

    代码:

    f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

    解析:这里的max中的两项就是当前状态的值了,为何?
    因为每种背包都是无限的。当我们把i1N循环时,f[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。

     

    多重背包:

     

    多重背包可以这么理解遍历到当前物品时如果物品的总体积小于背包的剩余体积那么就可以看成是一个01背包问题,如果大于的话就可以看成是一个完全背包问题;这样多重背包问题就能解决了。

     

    完全背包还有一个插曲:二进制优化,这样可以大大的节省时间,因为任何数都能由几个二进制数和一个非二进制数组成,比如9=2*2*2+1;这样就能将完全背包转化为01背包,将二进制数看成物品,取还是不取,找到最有的解决方案

    最长上升子序列:

    给定一个数列:ans[]={1,7,3,5,9,4,8};

    显然此序列的最长上升自学列为1348,长度为4

    怎么求?

    用一个数组DP[i]表示以ans[i]为结尾的子序列的最大长度,DP[i]中的每一个元素对应每一个状态。

    ans[]:1  7  3  5  9  4  8

    从头开始->  DP[0] :异地一数结尾的长度肯定是1;所以DP[0]=1;

                              DP[1]:ans[1]=7,向前遍历,只有当ans[i]时,才能取DP[i]这个状态,但此时状态不一定是最有的,还需要向前遍历完,知道遍历到数组尽头,选出最长的一个状态来,DP[1]=最长状态+1

                              For(int i=0;i

                              {

                                       int maxn=0;

                                       for(int j=0;j<=I;j++)

                                       {

                                                If(ans[j]maxn)

                                                {

                                                         Maxn=dp[j];

                                                }

                                       }

                                       dp[i]=maxn+1;

                              }

                      以此类推,将以每个数结尾的最优状态找出来,然后再找出最最优的状态;

    并查集篇:

    并查集就是合并查找,最短路径;

    1)“建树”:通俗的建树,就是将所有联通的点用一个标记标记出来,就形成了一个“数”,例如在1-10这十个点中,1 3联通那么1 3的就可以用相对小的1来标记。

    2)查找:想查找两个点是不是联通的时候,只需要找到两个点的根是不是同一个根,如果是那么就是联通,不是就不是联通。

    狄克斯特拉算法(dijkstra):


    1
    24是四个定点其他的是距离,从24最直接的就是2-4,但是不是最近的,需要舒展一下2-1-4,这样只有8.所以才是最短的。这个过程就是狄克斯特拉算法。下面进入正题:

     

    我们这里定义图的编号为:

    1 2 3

    4 5 6

    7 8 9

    1:初始化的图,其中包含边的权值(耗时)。(这里图是有向图)。

    2:确定起点,然后向能直接走到的点走一下,记录此时的估计值:2 6 9.

    3:找到距离起点最近的点,是正东边的那个点,这时候我们耗费权值为2。然后我们进行松弛操作,从起点到其东南方的点直接到的权值耗费为6,但是我们通过刚刚选定的点,我们找到了到这个点更近的方式,所以这个时候我们说从起点到其东南方向的点的权值更新值从6变成了5。这个时候我们就完成了第一次松弛操作。

    4:依旧是找距离起点最近的点。然后松弛我们发现这个时候从起点到其东南方的点的耗费权值从5又变成了4.这个时候我们完成了第二个松弛。

    之后的方式同上:选定距离起点最近的点v。然后通过点v进行松弛操作。我们发现能够通过增加走到目的地方式的复杂度(多转弯)的方式我们能够松弛掉权值,使得耗费的权值更小。
        
    模板:

    void Dij()//我们这里起点为1号编码点。我们这里的d[]表示从起点到这个点需要的权值。w[a][b]表示点a到点b这条边的权值.
    {
     int i,j,k,v,tmp;
     memset(vis,0,sizeof(vis));
     for(i=1;i<=n;i++)
         d[i]=w[1][i];//
    对应图不难理解,对于起点的初始化
     d[1]=0;
     vis[1]=1;
     for(i=1;i<=n;i++)//
    控制连接点的次数,例如上图,九个点,就循环九次。
     {
      tmp=N;//
    这里N表示无穷大。也就是图上的99.
      for(j=1;j<=n;j++)
      {
       if(tmp>d[j]&&!vis[j])
       {
        tmp=d[j];
        v=j;
       }
      }//
    每次我们都找到距离起点最近的点v
      vis[v]=1;
      for(k=1;k<=n;k++)//
    然后进行松弛操作。

    我们这里的d[]表示从起点到这个点需要的权值//加以强调其含义。

    {

        if(!vis[k])

       d[k]=min(d[k],d[v]+w[v][k]);

    }

    }

    }

     

     

    矩阵相乘:

     

    这个是在DP专题中,斐波那契数列:直接用线性递推来求f[n]复杂度是O(n),更适合球所有的f[1]f[n];如果用矩阵乘法算法的话计算K^n只有O(log n)的复杂度,只需要求出某个特定的f[n]是就占优了;

     

    数字乘矩阵:
    将每个位置的数字都与常数相乘

     

    矩阵加矩阵:
    将各个位置的数字相加

    矩阵乘矩阵:


    这个矩阵相乘怎么理解看了别人的博客才懂的,借鉴博客地址在文章开头;

    具体的就是线性方程:



    证明方法(借鉴):

    老实说,从上面这种写法,已经能看出矩阵乘法的规则了:系数矩阵第一行的21,各自与 xy 的乘积之和,等于3。不过,这不算严格的证明,只是线性方程式转为矩阵的书写规则。

    下面才是严格的证明。有三组未知数 xyt,其中 xy 的关系如下。

     

    x t 的关系如下。

     

    有了这两组方程式,就可以求 yt 的关系。从矩阵来看,很显然,只要把第二个矩阵代入第一个矩阵即可。

     

    从方程式来看,也可以把第二个方程组代入第一个方程组。

     

    上面的方程组可以整理成下面的形式。

     

    最后那个矩阵等式,与前面的矩阵等式一对照,就会得到下面的关系。

     

    矩阵乘法的计算规则,从而得到证明。

    具体怎么用呐:

    用于递推中,可以一程度的减时;

    例如:斐波那契数列,f[n]=f[n-1]+f[n-2];

    f[0]=0;

    f[1]=1;

     

    用两个矩阵模拟出这个递推关系,就可以了;

  • 相关阅读:
    七牛云同步脚本:qshell工具的使用
    JMS微服务架构 关于事务提交失败,自动重新提交的机制
    pve服务器内网同步时钟
    Doris 高可用集群的部署
    黑苹果(Hackintosh) 安装1:用 VMware pro 16 安装 Big Sur 11.6
    虚拟机安装 Win10 ,无法启动,报错EFI Network ... Time out
    黑苹果(Hackintosh) 问题,修改CPU数量和内存数量后,系统重启失败
    【ElasticSearch】docker安装es7.12.1,设置xpack密码
    【鉴黄】nsfw鉴黄
    【Redis】redis集群
  • 原文地址:https://www.cnblogs.com/wuwangchuxin0924/p/5781523.html
Copyright © 2020-2023  润新知