5月23日一周学习总结
二分问题
这周做题以二分问题为主,个人认为二分法比较适用于单调性问题,因为如果不满足这个条件,则问题会出现多个解,二分法同时也是对于暴力遍历的优化,因为二分的运算量是log级别的,在对于大数值的问题方面速度运算量是远低于暴力遍历的。
做题方面感觉二分的实现不难,但是切入点比较难找,例题:给出一条河,并给出n块石头距离河岸一头的距离,问搬去m块石头后,两块石头之间最短距离最大值为多少。
这道题一开始思考的时候想不到二分在哪使用,因为题目求解的问题只需按照距离由小到大删去就可以满足,所以打算用贪心,用差值数组把石头间的距离列出来,删去一个石头就把他的差值与上一个的差值合并,但是要先贪心排序,这样石头的顺序就打乱了,而且样例中还会出现连续多个石头被删去的情况,使用贪心实现比较困难,这时想到如果遍历答案的距离会不会简单一点,因为答案只有最短距离到河岸长度的范围大小,所以循环一次不会超时,这样就成了暴力遍历,所以就找到了了二分的使用之处,代码如下:
int find_(int be,int en)
{
while(be<=en)
{
int mid=(be+en)/2;
int c=0,st=0,la=1;
while(la<n+1)
{if(a[la]-a[st]<mid)
{
c++;
la++;
}
else {st=la;
la++;}}
if(c>m)
en=mid-1;
else be=mid+1;
}
return en;
}
其中有两点需要注意,1:为了应对连续删去多块石头,石头距离变化的情况,将上一块石头设置为起始点,这块石头为结束点,如果当前石头被删去,则结束点移到下一块石头,判断这时候新构成的距离会不会满足当前答案,如果不满足即不删去,就让起始点和结束点同时改变,起始点继承结束点位置,而结束点位置变为下一个石块位置。2:这样二分可能会在一个区间里有多个解,所以要保证取到区间最大值,所以即使当前距离满足答案后也要再往后二分查找,找到最大值。
主函数代码:
int main()
{
int w=0x3f3f3f3f;
cin>>l>>n>>m;
a[0]=0;
a[n+1]=l;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a,a+n+1);
for(int i=1;i<=n;i++)
{
w=min(w,a[i]-a[i-1]);
}
w=min(w,l-a[n]);
cout<<find_(w,l)<<endl;
return 0;
}
主函数中还有一步是去查找最小石块间距离,这一步要保证所有距离都被遍历,包括最后一块石头与河岸的距离。
例题:给出一个数,如果是素数就输出零,如果不是就输出离它最近的两个素数的差。
这道题类似于上一道搬石头的题,但是有素数处理这一步,之前做的有关判断素数的问题都是用一个循环来解决,从二除到他的算术平方根,若不能被整除,则说明是素数(2要单独判断),但是这个题需要找到素数,而不是判断素数,所以要打印素数表:
void build()
{
memset(a,1,sizeof(a));
for(long long i=2;i<=maxn/2;i++)
{
if(a[i]!=0)
{for(long long j=2*i;j<=maxn;j+=i)
{
a[j]=0;
}
}
}
a[1]=1;
a[2]=1;
}
这一步处理是第一次见到,本质上和判断素数是一样的,是对所有数的判断,这里用一个数的所有倍数来剔除所有非素数,值得注意的是倍数的循环是从二倍开始的,不然会误判除以自身也是非素数最后还要对2进行单独判断。(值得优化的地方还是挺多的,看了别人的代码发现对于这种大计算量的问题思维要更加缜密,不然很容易超时)
主函数代码:
int main()
{
long long n;
build();
while(cin>>n)
{
if(n==0)
break;
if(a[n]==1)
cout<<"0"<<endl;
else
{
long long sum=2;
long long be=n-1,en=n+1;
while((be!=0)&&(a[be]==0))
{
be--;
sum++;
}
while(a[en]==0)
{
en++;
sum++;
}
cout<<sum<<endl;
}
}
return 0;
}
主函数里值得时注意的点只有对距离的判断,首先是把两个最近的素数按照输入数分为两个区间,分别从两个区间判断距离,这是默认的距离应该为二因,然后左右遍历即可。这道题目虽然是二分的里的,但是想了很久没想到二分的具体应用方法,上网看了别人的二分方法,发现是二分查找距离给出数最近的素数,可以是比他大的那个素数,这时候只需用一个数组把所有素数存进去,然后让找到的最近的素数与比他小的那个素数相减就能得到答案,确实优化了代码,两次循环变为一次。
(附上别人二分的代码)
while (scanf("%d",&n)&&n)
if (!have[n]) printf("0\n");
else
{
l=1;
r=tot;
while (l<r)
{
mid=(l+r+1)/2;
if (prm[mid]>=n) r=mid-1;
else l=mid;
}
printf("%d\n",prm[l+1]-prm[l]);
}
感觉二分的题目共同点比较多,实现的代码相似度也比较高,但是问题的形式比较绕,找出二分的点才是重点。
一周学习总结
这周主要是训练二分题目为主,同时也学习了搜索,包括DFS和BFS,一个深度优先搜索,一个广度优先搜索,还接触到了递归。递归类似于动态规划,都是将当前问题划分为多个小问题,然后对其求最优解,递归在实现深度搜索的时候很重要,深搜如图,即搜索每一条路线,从开始节点搜索到结束节点。
回溯,当一个节点走完的时候,要返回到上一个分支点,选择另外一条线路再次开始走
深搜的代码模板:
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;
}
}
深搜的题目一般都是在求解可行方案数,广搜一般是最优解。