贪心算法(学习报告)

     

又经过了一周的学习,这一周课上学习了几个贪心类型的题,给我印象比较深的有三个“钓鱼问题”,“赏金猎人”,“学生的复仇”,这三个题可以说每个题都有自己的特色,赏金猎人和钓鱼问题还有了点新讲的动态规划的意思,学生的复仇又牵扯到了“博弈问题”(让我想起了之前做的巴什博弈),自己在做题过程中主要解决了三个,一个是“搬椅子”,另一个是“装箱子”,再一个是“酸奶生产”。自己写完了之后又看了一下其他大佬的代码,无论从思路还是代码上都非常的厉害,至少我现在还没法直接写出这种,以后多去看看他们代码然后多去学学。

     下面还是和上次一样的,我先总结一下上课的内容然后再总结自己做题的内容
一.课上
     1.“赏金猎人”
     题目意思:就是有n个城市,每个城有不同的金币,两个赏金猎人到了就能拿走,一共T天,在两个赏金猎人距离不超过K个城市的情况下能拿到的最多的金币数。
思路:看着其实贪心的影子非常明显,先模拟一下,发现两人分头走拿金币要比一起走拿的多,然后分头走又分从哪里开始,从每一个地方开始走T天拿到的金币都不同,全部枚举出来,进行排序就可以得到最大值
     注意点:既然要枚举,那么也要注意时间复杂度,每一个方法走的路径的规定好了,接下来就可以“增首删尾”来枚举每一个情况下的金币,没必要把时间复杂度上到平方。

     2.“学生的复仇”

题意:学生给学生会主席提出n个提案,筛选通过p个,这P个提案主席可接受k个拒绝p-k个,但每个提案都有相应的“劳累值”和“拒绝后的领导不满意度”。问你,提出p个方案,那些提案可以让学生会主席劳累值最大,而且这时他拒绝的那些个提案又可以让领导不满意度最大。

思路:博弈问题,双方考虑
1.学生:学生会主席按照领导满意度来拒绝,那么按照“满意度”升序,前面n-p个学生会主席肯定拒绝,但是相同满意度的情况下,他肯定先拒绝劳累值最大的,所以
先按照“满意度”升序,“满意度”一致的情况下,按照“劳累值”降序
前n-p一定会被拒绝。
2.在已经通过的p个里面,还想让主席的劳累值最大,那么久先按照“劳累值”降序,为了确保了前面k个必然是主席不能拒绝的,再让相同“劳累值”下“满意度”降序

     3.小技巧:
     **1.**在寻找一个字符串中某个第一个字符的下角标可以使用

  string s;
        cin >> s;
        int i = s.find_first_of('*');

     这样的话就能不用谢for从而实现快速;

     当我们要去从后往前找到第一个特定字符的位置,就没啥方便的函数,但是可以这么写

string s;
cin>>s;
for(int j=s.size();j>=0&&j!='你想找的字符';j--
{}

也不复杂对吧
总比(写个for循环然后套if最后找到了再break)强
2.string的获取子串
substr

string s,ss;
cin>>s;
substr(begin(开始位置),length(获取长度));
比如123456
获取substr(13)
获得234

3.复习一个“find”函数

#include 
/*关于find的返回,返回的是一个下标,如果查找到了,就返回第一个匹配到的下表,否则就返回一个特定的nops*/

using namespace std;
int main()
{
    int t;
    for(cin>>t;t;t--)
    {
    string a,b="abc";
    cin>>a;
    cout<<a<<" "<<b<<endl;
    cout<<a.find(b)<<endl;
    }
    return 0;
}

/*5
a
a abc
4294967295
b
b abc
4294967295
c
c abc
4294967295
abc
abc abc
可以看到find的匹配是全匹配,nops是一个定值
0*/

三.刷题
1.字符替换
题意:给出一个长度为n的字符串s,s由*和.组成,要求你用x替换掉第一个 * 和最后一个 *,而且每两个相邻的x之间不能超过距离k
输入:n,k,s
输出:最少的用到x的个数
思路(WA):找出第一个和最后一个 * 然后把这两个下标记录,用这两个下标之差除以k+1,如果这俩相等,那么直接输出1结束

思路(AC):从第一个 * 开始,直到最后一个 * 之前,i不停地遍历,如果i-第一个*的坐标>k,用一个x,更新“坐标”位置

明明第一个思路也是可以AC的,,,心太乱了没写下去
注意点:

#include 
using ll = long long;
using namespace std;
int main()
{
   int t;
   for(cin>>t;t;t--)
    {
        ll n, k, res = 1;
        cin >> n >> k;
        string s;
        cin >> s;
        int i = s.find_first_of('*');
        while (true)
        {
            int j = min(n - 1, i + k);
            for (; i < j && s[j] == '.'; j--)
            {
            }
            if (i == j)
                break;
            res++;
            i = j;
        }
        cout << res << endl;
    }
    return 0;
}

中间那个for循环,他的意思是从第一个* 开始不断向后方寻找
2.装箱子
给出一个6
6的容器,和11,22到66不等的多个箱子
问:最少用多少个6
6的容器可以把这些箱子全部装完
思路:
从66开始,这个独占一个
5 * 5,可以独占一个,但同时可以带着11个1 * 1一起
4 * 4,可以独占一个,同时也可以带着2 * 2和1 * 1一起
3 * 3,4个可以独占一个
在这里就出现了复杂情况
因为3
3带着22时会有4种情况
所以应当分情况讨论
在前面4种讨论完了之后
可以用原本2
2的数量减去44和33用到的尽可能多的22的数量、
如果2
2还有剩余,自动分配箱子
然后再是1 * 1,减去5 * 5,4 * 4,3 * 3,2 * 2用到的尽可能多的11的数量
如果还有剩余,分配箱子
注意点:
对于3
3出现的情况,我可以用4个if来实现
但是这样太过于麻烦了
可以用一个数组提前存放3*3的4种情况,然后用
a[x%3]就可以自动匹配到相应的情况。
3.搬椅子
这个题和某C0讨论了一下,说是可以有这么几种思路
一种是合并到同一列,改写他的走廊冲突规则(没试过)
另一种:查看从最开始的走廊段到最末尾的走廊段被占用的次数
占用次数最多的,那么时间就有这段走廊来决定了
原本想的是大段大段的来
后来我给改成了一小段一小段
注意点:好好看看题,,,这个题居然房间分南北不说还分单双,,
4.寻找子串
题意:给出字符串a和b,问你通过多次只删除首或位字符让a,b一致
思路:因为之前做过一道div3的题是这样的:给出两个字符串,a,b问通过多次删除首字母让ab一致

但是这已经不是一个思路了,原本那个题的思路是,既然只能从前面删除,那么只要从后面遍历,只要有不一样的,从这里开始把前面全部都删除就行了

这个题是可以把后面的也删,这就变成,遍历a,b的全部子串然后判断是否匹配,如果匹配的话那么最长的可以匹配的子串有是哪个。

思路没问题了,代码有问题了:
我的一开始菜狗代码就不上了,,

#include 

using namespace std;
int main()
{
    int t;
    for(cin>>t;t;t--)
  {
    string a, b;
    cin >> a >> b;
    int n = a.size(), m = b.size();
    int ans = 0;
    for (int len = 1; len <= min(n, m); len++)/*len最少是1,最长是*较短子串的长度*/
    {
        for (int i = 0; i + len <= n; i++)/*在len+i,也就是len的范围遍历a的子串*/
        {
            for (int j = 0; j + len <= m; j++)/*遍历b的子串*/
            {
                if (a.substr(i, len) == b.substr(j, len))/*匹配子串*/
                {
                    ans = max(ans, len);/*总结长度*/
                }
            }
        }
    }
    cout <<a.size()+b.size()-2*ans<<endl;
  }
      
  }

其实思路都是一样的,甚至代码都很像,就是遍历的过程中,我需要让A,B遍历每个子串,我用的方法很是麻烦(for),但是这里用了个substr就好多了

你可能感兴趣的:(学习路很长)