2021-05-22

ACM学习总结之二分法和搜索算法

这周是ACM课的最后一周,做了一些二分法的题目,上课时又学习了搜索的相关知识。
相比于dp,二分法难度稍微低一些。
题目1:
题意:给出四个数列,每个数列选一个数,使这四个数相加为0,求共有多少种情况。

这个题和上课讲的一道题目比较类似。上课讲的题目为三个数列中各自取一数,求是否有和等于某一定值(即a+b+c=s)。最笨的方法为三重循环列举各个数组的值求和并与所给定值相比较,相等则符合。二分法的话是对它的简化,二分法是定义一个新数组(d[i]),其为两个原数组各自取一个元素相加的值所组成的新元素的所有值的集合,原式可转换为d+c=s,到了这里,可以二重循环列举d数组和c数组的值与s进行比较,但进一步优化,便成了二分法,即d+c=s变形为d=s-c,用二分法就是在d数组中查找是否有元素等于s-c,就是先固定s-c的值,判断所有d是否有满足情况的案例,然后遍历s-c的值。回到本题,这道题比起上面讲的题,多了一个数组,而且所求为共有多少情况而不是是否有情况满足。简化这个题,类似于上一个题来合并数组,怎么合并?可以先合并前两个数组从,产生一个新数组,再将这个新数组与第三个数组合并,便转化成了d+c=s的问题。也可以合并前两个数组,同时合并后两个数组,也转化成了d+c=s;
这里s等于0,即d=-c;那么进行二分法,先固定一个数组的值,查找另一个数组中是否有元素满足条件,然后遍历数组的值。
代码

#include<iostream>
#include<algorithm>
using namespace std;
long long int a[4005],b[4005],c[4005],d[4005];
long long int sum1[4005*4005],sum2[4005*4005];
int ans=0,k=0;
void find(int a)
{   int l=0,h=k-1,mid;
    while(l<h)
    {   mid=(l+h)/2;
        if(sum2[mid]<a)
            l=mid+1;
        else h=mid;
    }
    while(sum2[l]==a&&l<k)
    {
        ans++;
        l++;
    }
}
int main()
{
    int n,i,j,l=0;
    cin>>n;
    for(i=0; i<n; i++)
    {
        cin>>a[i]>>b[i]>>c[i]>>d[i];
    }
    for(i=0; i<n; i++)
    {   for(j=0; j<n; j++)
        {   sum1[k]=a[i]+b[j];
            sum2[k]=-c[i]-d[j];
            k++;
        }
    }
    sort(sum1,sum1+k);
    sort(sum2,sum2+k);
    for(i=0; i<k; i++)
    {
        find(sum1[i]);
    }
    cout<<ans<<endl;
    return 0;
}

题目二:
题意:输入几个字符串,对于某一个字符串,它前面的字符串中若没有与之相同的,则输出“OK” ,若有与之相同的,则输出一个字符串,这个字符串为原字符串的基础上加上一个数字,数字的值为有几个与之相同的字符串。

这道题刚上来感觉思路很简单,用朴素的方法提交后却超时了,然后看了看题解,发现大多用的map,于是这道题让我复习了一下map的有关知识。刚上来我想,先定义一个初值为‘1’的字符型数 t 和一个字符串型 d ,对于除第一个字符串以外的每一个一字符,先将它的值赋给d,然后将它前面的字符与之一一比较,若前面每有一个相同的,则将字符串的值用字符串的原值d加上t来替换,t递增 , 比较完后,若t还是原来的值‘1’,则表示当前字符前没有与之相等的字符,则输出“OK”,否则表示有与之相等的元素 ,则输出更新后的字符值。
代码如下

#include<iostream>
#include<string.h>
using namespace std;
int n;
string  a[106];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        string d=a[i];
        char t= '1';
        for(int j=1;j<i;j++)
           if(a[j]==a[i])
            {a[i]=d+t;
            t++;}

           if(t!='1')  cout<<a[i]<<endl;
           else cout<<"OK"<<endl;
    }
}

提交后超时了。 用map来做,先定义pair类型数组map<string,int>mymap,实值初始值为零,对于每一个pair型,输入键值(字符串),先判断实值mymap[str]是否为零,是否为零对应不同的操作,然后不论实值是否为零,实值进行自增操作;判断实值是否为零时,若为零表示,实值没有更新,即前面没有相同的字符串,就输出“OK”,若不为零,则表示实值更新了,就输出原字符和实值。用map不用进行大量的字符比较,虽然代码长度没有发生太大的变化,但实质上却简单多了。
代码

#include<iostream>
#include<map>
using namespace std;
int main()
{
    int n;
    string str;
    map<string,int>mymap;
    cin>>n;
    while(n--)
	{
        cin>>str;
        if(mymap[str])
        cout<<str<<mymap[str]<<endl;
        else
        cout<<"OK"<<endl;

        mymap[str]++;
    }
    return 0;
}

题目3:
有一条河,河的长度已知,河中间有一些石头,石头的数量知道,相邻两块石头之间的距离已知。现在可以移除一些石头,问移除m块石头后,相邻两块石头之间的距离的最小值最大是多少。

分析:
这道题上课讲过,但上课的时候没有完全理解,课下又看了看才理解了。移除石头后,相邻两块石头间距离的最小值越小,说明移除的石头越少,相邻两块石头间距离的最小值越大,说明移除的石头数量越多。求相邻两块石头之间的距离的最小值的最大值是多少,先设一个最大值,这个最大值的初始范围为0到L(起点到终点的距离),然后通过一个函数来找到这个最大值需要移除的石头数并与m比较,若移除的石头数比m少,则说明所给的最大值偏小,若移除的石头数比m多,则说明所给的最大值偏大,通过二分不断更新上下限,来得到最终结果。
代码

#include<iostream>
#include<algorithm>
using namespace std;
long long int L,m,n;
long long int a[50002];
bool check(int now)
{
    int res=0,p=0;//p为前一个石头到指向石头的距离 
    for(int i=1;i<=n;i++)
    {
        if(a[i]-p<now)
        {
            res++;
        }
        else
        {
            p=a[i];
        }
    }
    return res<=m;//注意这里的等于
}
int main()
{
   cin>>L>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];a[++n]=L;
    sort(a+1,a+n+1);
    int l=0,r=L,mid;
    int ans=0;
    while(l<=r)
    {
        mid=l+r>>1;
        if(check(mid))
        {
            ans=mid;
            l=mid+1;
        }
        else
        {
            r=mid-1;
        }
    }
    cout<<ans<<endl;
     return 0;
}

注意若所找的res若等于m,得到的结果不一定是最优解,因为移除m块石头,所得到的相邻两块石头间距离的最小值的最大值可能有多种结果。求最大值,等于时和小于时相同处理,因为要查大的,偏右查找。
做类似这个移除石头的题目,比如“将n根网线切成k段相同长度的网线,问可切成的最长长度是多少”,这种题目求最大值(或最小值),那么先假设一个最大值k(或最小值),再将用这个值所得到的值与n比较,以此通过二分不断更新最大值的范围最终得到所求值。

这周讲搜索,先讲了讲一下递归,递归在上一学期就涉及到了,递归是函数自己直接或间接调用自己的算法,循环嵌套。
递归主要是求出递归函数和终止条件。搜索的话上课讲了两种,一种是深度优先搜索(dfs),这种搜索方法对每一个分支一步到位,即每次选一个分支,再选这个分支的一个分支,再选一个分支……另一种是广度优先搜索(bfs),为一层一层的来。

例题:
设有n个整数的集合{1,2,…,n},从中取出任意r个数进行排列(r<n),试列出所有的排列。
分析:从n个数中取r个排,用深度优先搜索,先排第一个数,再排第二个数直到排完第r个数。定义两个数组,一个a[k]表示排的第k个数的值,b[i]表示i数是否被排过。对于第k个数,从i=1到i=n遍历直到找到可用的值,将i值赋给a[k],然后更新b[i]的值。

#include<cstdio>
#include<iostream>
#include<iomanip>
using namespace std;
int num=0,a[10001],n,r;
bool b[10001]
int search(int); 
int print(); 
int main()
{
  cin>>n>>r;
  search(1);
  cout<<num<<endl;     
}
int search(int k)
{
    int i;
    for (i=1;i<=n;i++)
     if  (!b[i]) 
      {
         a[k]=i;    
         b[i]=1;
         if (k==r) print();
            else search(k+1);
         b[i]=0;
      }
}
int print()
{  num++;
  for (int i=1;i<=r;i++)
    cout<<a[i];
  cout<<endl; 
}

需要注意的是代码中的b[i]=0,这个步骤是必须的,因为对于下一个排列,上一个排列不能对它造成影响,所以排完一个排列后,要使排的数可用。

回顾这学期的ACM课程,有收获,也有遗憾。遗憾的是,有些题目不能按时完成并深度理解,课前预习工作不是很到位导致上课有时候听不懂,由于上课老师讲的题目题干都是简写的,如果不事先预习一下,很可能导致还没弄清题意,老师已经讲了很多了。收货要远大于遗憾,我的水平得到了一定的提升,了解了一系列算法的知识并得到了初步的应用,自己的思维方式也有了一系列改观。总之,这学期学这门课,我不后悔!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值