课程总结第四周

动态规划:课上所讲跟我原先开始预习时对于它的理解不一样,思路和代码实现的方法都很不一样,所以开始时造成了一定困难。学习思考过后,才将自己的思路纠正过来,也加深了对动态规划定义的理解。
现在看来:
1.DP的关键在于找到状态转移方程,枚举子问题的各种可能情况,再进行计算(对于金币那题就是根据走过路径更新各个位置上的金币数)。
2.当前每一个状态都会影响下一个状态的决策,并且都是根据状态转移方程进行的。子问题的最优值便是源问题的解。
3.子问题的结构和源问题的结构是一致的。
4.DP好像和分治法类似,然后我去稍微看了看,只是简单了解。对于子问题重复计算引起的复杂度区别。
求解步骤:
1.将问题拆分。
2.定义问题状态和状态之间的关系。
3.将每个状态最优解保留下来,为了在下一步推导时使用前一步的最优解,过程中必然舍去相比较的差解。
(这些都是理论知识,大致框架,最重要的还是分析题目)


之前我认为,一个动态问题可以分成若多个子问题,得到各个子问题的最先值,那么开始时如何规划便显而易见。
1.比如那个吃金币的问题,我的思路是反正这个人都会从左上端走到右下端,那么便从右下端开始考虑问题,分析从哪里走到这金币最多,然后用下方的金币逐层更新上方的金币,当更新到离起始位置只剩一格时,那么往哪里走便很明显,起始位置便是最大金币值。
收获:我开始这个思路没有问题,当然其实复杂度是一样的。我开始的想法比较麻烦,多此一举,此题真的没必要逆向考虑,但还是要有这种思想。
我的思路敲出的代码:

int a[10005][10005];
int main()
{
    
       memset(a,0,sizeof(a));
    int m,n,s;cin>>m>>n;
    for(int i=0;i<m;i++)
    {
    
    
        for(int j=0;j<n;j++)
            cin>>a[i][j];
    }
    for(int i=m-1;i>=0;i--)
    {
    
    
        for(int j=n-1;j>=0;j--)
        {
    
    
           a[i][j]=max(a[i+1][j],a[i][j+1])+a[i][j];
        }
    }
    cout<<a[0][0]<<endl;
    return 0;
}

课上思路:

int a[10005][10005];
int main()
{
    
    
    int m,n,s;cin>>m>>n;
    for(int i=1;i<=m;i++)
    {
    
    
        for(int j=1;j<=n;j++)
        {
    
    
            cin>>s;
            a[i][j]=max(a[i-1][j],a[i][j-1])+s;
        }
    }
    cout<<a[m][n]<<endl;
    return 0;
}

2.然后是最长子序列问题。给出一个数组序列,然后无关乎坐标,得到一个最大长度的子序列
我的思路:开始我并没有将他当作一个动态数组问题,而是用一种贪心思想。先找到最大位置的下标,然后往前遍历,记录一共多少个数比它小,然后加1。之所以取最大数,是因为这样能保证有足够多空间的数比他小,比如9,就会有1-8这段空间,有更大可能取得最长序列。之后我发现,9有可能在序列的前面位置,便改进了思路,n个数共取n次最小值。
然后看完课上代码,发现大致思路相同,不同点在于不必要每次取最大数了,因为反正都要取n次,所以每次取最大值便没了意义,便简化了思路,代码也更容易写出了。
DP .T题收获:
(此题虽不难,但我wr了4次,超时2次,提交错误1次,最后才ac,其实代码都是一样的,就是细节没做好,所以同样的代码距离能够ac有时只是一些细节的完善)
1.ios::sync_with_stdio(false); cin.tie(0);这段加快输入的代码我开始没有重视,结果发现非常有效,同样的代码快了将近500 ms。最好放到main函数的开头位置。
2.从子序列末尾找到子序列的开头位置。若不同子序列有相同的结果,去掉break,便是找到第一个这样的子序列。

 for(int i=g;i>=1;i--)
  {
    
    
    temp+=a[i];
    if(temp==maxn)
    {
    
    
      s=i;
      break;
   }

3.最长公共子序列的问题。题目:两个字符串,找出公共部分的数量。----->>>这题代码和思路其实我都听懂了,但总感觉差点什么。最后明白是差了分析这个问题的方式,这种方式是老师上课没有提及的。
收获:将整个求解过程用一个最优决策表来描述,行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对此问题在某个阶段某个状态下的最优解,填表的过程就是根据递推关系填写表格而本题可制作一个二维表格,列是字符串A的元素,行是字符串B的元素,填写表格,在第i行第j列下的数字便是当前最大公共字符数。

总结几道codeforces上的题目:
一. D . Epic Transformation
大致题意:给定一个数组,每次删去两个不同的输,问这个数组最少剩下几个数。
我的思路:第一下就想到了要用优先队列,每次将最大一个数pop,然后将优先队列进行遍历,若有一个和最大值不一样的,便在队列中删去这个数;然后再取一个最大值,删去一个不一样的。最后可能情况:pop出的的最大值和队列内的数一样,或者队列为空。但是遇到几个问题。
1.不知道如何对优先队列进行遍历操作。之后了解到只能用循环不断的pop,和push。
2.优先队列好像无法删除队列内某个特定的元素。
收获:最后发现这个思路其实行不通,有个样例通不过。删除的一对元素是需要组合的,不能随意删除两个不同的元素。最后,只能去看别人写的代码,其实思路对了一半,确实要用到优先队列,但要配合map进行使用。我照着别人的思路敲了一遍,发现自己还是不会用map,对它的一些操作还不熟。包括要使用到的迭代器,虽然没系统学过,但这也不是理由,简单了解下来已经大致会用,算学到了

int main()
{
    
    
    int t;cin>>t;
    while(t--)
    {
    
    
        int m,n;cin>>n;
        map<int,int>cnt;
        for(int i=0;i<n;i++)
        {
    
    
            cin>>m;cnt[m]++;   //以数组形式对map进行存储,first是这个值,second是这个数出现的次数
        }
        priority_queue<int> q;
        map<int,int>::iterator it;    //学到的迭代器,和指针好像类似
        for(it=cnt.begin();it!=cnt.end();it++)
        {
    
    
            int r=it->second;
            q.push(r);             //将second存入优先队列q;
        }
        int ans=0;
        while(q.size()>=2)
        {
    
    
            int a=q.top();q.pop(); 
            int b=q.top();q.pop();
            ans+=2;
            a--;b--;            //每次取出出现次数最多的数进行消除
            if(a) q.push(a);   
            if(b) q.push(b);    //再次存入
        }
        cout<<n-ans<<endl;
    }
    return 0;
}

然后我又发现了一个更好,更精妙的方法,既不要用到priority_queue也不要用map,直接找出消除数字的规律来,只要用组循环便可解决问题。但这个方法巧妙,便意味着很难想到。事情就是这样,想省力就要找到多花心思;不想探究更多,便要多出力。
思路:将数组升序排列,将第一个数,和比它大(n/2+1)的数进行组合消除,非常巧妙,虽然我也不知道什么原理,但就是能ac。

int a[200005];
int main()
{
    
    
    int t;cin>>t;
    while(t--)
    {
    
    
       int n;cin>>n;
       for(int i=0;i<n;i++)
       {
    
    
           cin>>a[i];
       }
       sort(a,a+n);int num=n;
       for(int i=0;i<n/2;i++)
       {
    
    
           if(a[i]!=a[(n+1)/2+i]) num-=2;
       }
       cout<<num<<endl;
    }
    return 0;
}

二 .A. GCD Sum
这题是div.2的第一题,所以我是ac的。便大致总结一下:就是将一个数和它各个位上数的和取最大公约数,不为1即可,可进行递增。我用了两个普通函数,一个求各个位上的和,一个求最大公约数(当时想到可用递归来加快运行速度,虽然不是很理解,但也能大体写出)
收获:最大公约数的递归,便当做学习递归的起点,开始吧。

int gcd(ll a,ll b)
{
    
    
    return b==0?a:gcd(b,a%b);   // 最大公约数的递归
}

三.B. Box Fitting
第二题,这题一直困扰了我很久。
大致题意:往一个大箱子里装盒子,盒子高度都为1,长度不等,给定大箱子的的长度w,问最多需要装几层---->>>我看出这是个贪心的问题。第一个想法是觉得这题和那个装包裹的题很像,凡是大于w/2长度的高度累加1,剩下的空间放那些小的,将小的排好序,慢慢放进去。但是这样做很麻烦。----->>>于是想到先将这些小箱子降序排列,先放一个大的,再从末尾挑小的放进去,直到放不下为止,然后高度+1,再放一个大的,重复此操作。这样思路更清晰,代码也更好写。---->>>然后又发现了一个特别好的方法。又是要用到优先队列,先将数组从大到小排好序,将(w-数组末尾元素值),也就是剩余空间,放入优先队列,不断的放和更新,直到出现一个数组元素大于这个剩余空间,高度累加1。很精妙!!


以上这就是本周的感悟和收获。虽然本周没有要求写总结,但我还是写了4000多字,也算是警醒自己:有些事情有时,需要一个理由;而有些事情,仅仅是因为想做。

猜你喜欢

转载自blog.csdn.net/weixin_51934288/article/details/115336259