• 基础算法学习笔记


    贪心

    2020.9.12将一本通上的贪心学的差不多了,现在就整理一下。

    1.选择不相交区间问题

    例题:P1803 凌乱的yyy / 线段覆盖

    分析:板子题,没什么好说的,总之就是每次贪心选择活动时间尽量少并且与之前的活动不相交的,不多评述。

    2.区间选点问题

    例题:P1250 种树

    分析:给定许多区间,叫你取尽量少的点达成每个区间都至少有一点的目标。解决方案就是每一次选择区间末尾的那个点,使得这个点能同时存在于尽量多的区间内。

    3.区间覆盖问题

    例题:#10002. 「一本通 1.1 例 3」喷水装置

    分析:给你几个区间,让你选几个区间覆盖一个大的。解决方案就是先把区间按左端点大小进行排序,每次再贪心选择右端点最大的,直到将区间覆盖为止。

    4.流水作业调度问题

    例题:P1248 加工生产调度

    分析:这种类型的题属比较难的,但我做了这么久的题也没见过几回,就这一道。有(M_1)(M_2)两台机器,要现在(M_1)上加工,再到(M_2)上加工。思路是要使用(Johnson)算法:让(M_1)没有空闲,让(M_2)空闲时间尽量短。

    可以证明得到一个结论:(N_1)(a<b)的作业集合,(N_2)(age b)的作业集合,将(N_1)的作业按(a)非减序排序,(N_2)中的作业按(b)非增序排序,就可达成最优顺序

    因为感觉实在不会来考,所以不准备证明,想看代码实现和证明看书就行,不在这里多扯。

    5.带限期和罚款的单位时间任务调度

    例题:P1230 智力大冲浪

    分析:将所有事件根据罚款大小排序,从罚款多的开始处理,再一个个看能不能安排在所需的时间段内,不能就放弃处理。

    6.习题

    简单的习题就不写了,这里只给出两道比较难的。

    1. P1717 钓鱼

    2. P2512 [HAOI2008]糖果传递

    思路就不想再讲了,时间没那么多,有时间再自己去看题目。

    总结

    实际上,考场上纯考贪心的题实在不多,所以此处讲的这些价值大不大?着实是个问题。但是要知道的是,考场上与贪心结合起来考的题非常多,所以贪心的思想一定要掌握好,要把握住贪心的精髓。

    二分

    2020.10.12日时学习,刚好与贪心隔了一个月(笑)

    二分查找就不说了,傻子都会。

    重点:二分答案

    二分答案怎么说呢,入门题应当是这一道P1182 数列分段 Section II,其实当时理解还很浅薄,虽然将题解看了几遍,但还是处于云里雾里的状态,懵懵懂懂的就过了,后面在看一本通时终于有一次豁然贯通,才理解了二分答案。

    二分答案,顾名思义,我们可以根据题意确定答案的上下界,然后不断二分,确定一个答案,接着通过一个判断函数来判断这个答案是否符合要求,接着就是缩小范围,直到找到那个最优解。

    适用题目类型:最大值最小或最小值最大,如果出现这两种字眼,那不用怀疑,妥妥的是二分答案了。

    板子题挺多的,就不一一列举,但再在整型二分之外还有另一种二分类型,更难的实数二分,对于这种类型的题目,精度一定要格外注意,稍不留神便会被精度坑死。

    例题:P3743 kotori的设备

    想了想,还是放一个板子

    while(r-l>=1e-6)
    {
    	double mid=(l+r)/2;
    	if(judge(mid))l=mid;
    	else r=mid;
    }
    

    嗯,精度要开大一点是比较保险的,不过建议也不要太大,1e-12就有点过分,一般1e-8是完全够用的。

    冷门的三分法

    先放一个模板传送门:P3382 【模板】三分法

    如果说二分是专门针对具有单调性的问题的话,那么三分就一定是针对具有单峰性的问题。与三分有关的问题,其答案范围构成的函数一般都具有一个单峰或低谷,分别对应答案的最大值,最小值,模板如下。

    while(r-l>=1e-6)
    {
    	double m1=l+(r-l)/3.0,m2=r-(r-l)/3.0;
    	if(f(m1)<f(m2))l=m1;
    	else r=m2;
    }
    

    f函数就要根据题意设定,意为答案取此时的函数值。

    三分习题

    1. P5931 [清华集训2015]灯泡

    简单说一下,其实也没什么好说,利用初中数学知识再套三分模板就可过,分类讨论很淦。

    1. P2571 [SCOI2010]传送带

    没有做好心理准备不建议乱上。函数带两个变量就很淦,一定要先确定一个变量,需使用三分套三分的方法。另,这里的double多的让我快打吐。

    总结

    二分思想一样很重要,虽然其实在考试里出现的似乎不多,不过基础算法毕竟是基础,学好是很必要的。至于三分?有兴趣的可以学一学,没兴趣的让它爬就行,CCF要是出三分题就是脑子进水了。实数二分不用太担心,因为精度问题实在是太少出现在考场上。总之还是四个字:领悟思想

    真正的重点:搜索

    FIRST 深搜(dfs)

    先放一个网上找来的模板

    void dfs(答案,搜索层数,其他参数){
        if(层数==maxdeep){
            更新答案;
            return; 
        }
        (剪枝) 
        for(枚举下一层可能的状态){
            更新全局变量表示状态的变量;
            dfs(答案+新状态增加的价值,层数+1,其他参数);
            还原全局变量表示状态的变量;
        }
    }
    

    搜索,尤其是深搜,对于非常多题都适用,跟枚举的纯暴力相差无几,容易打,不易出错,很多题用来对拍的暴力都是这种。它的时间复杂度是指数级别的,但是有时题目的正解,可能就是深搜这种暴力,正常跑是肯定跑不过去,但若是能优化,结果或许就会大不一样。

    说了一堆废话,回到正题。这里的主角是深搜,但是却是有剪枝的深搜,剪掉深搜的搜索树上的那些无用枝条,使得深搜能在极短的时间内找出答案,这便是我们的正解。

    但却有三个加剪枝一定注意:正确性,准确性,高效性。做到这三点,那才能算真正优化到了。

    优化技巧有很多,我现将书上的分类搬下来,以便观看。

    1.优化搜索顺序 2.排除等效冗余 3.可行性剪枝 4.最优性剪枝 5.记忆化

    先来看一道剪枝的入门题P1025 数的划分

    这里使用搜索可以说是很明显了,要打出来也很简单,但是分析一下复杂度就知道,TLE是稳稳的。所以,这里一定要加剪枝。如何剪枝呢?其实也很简单,只要分析一下上下界即可,因为不考虑顺序,所以可以设(a[i-1]le a[i]),现在下界已经有了,也可以轻易推出上界是(frac{m}{k-i+1}),只要加了这两个简单的剪枝,就可以愉快的AC了。

    再来看一道剪枝的经典题目P1120 小木棍 [数据加强版]

    这道题的剪枝异常之多,大类里可以分出最优性剪枝与可行性剪枝两类,根据题目具体细节就可以写出很多剪枝,是一道很好的剪枝练习题。

    经典题目还是放一下代码

    代码
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int a[105],n,len,m,minn,sum,bj,tot,b[105],next[105];
    bool ex[105];
    bool cmp(const int &x,const int &y)
    {
    	return x>y;
    }
    void read()
    {
    	cin>>n;
    	int d;
    	for(int i=1;i<=n;i++)
    	{
    		cin>>d;
    		if(d>50)continue;
    		a[++tot]=d;
    		sum+=d;
    	}
    	sort(a+1,a+tot+1,cmp);
    	next[tot]=tot;
    	for(int i=tot-1;i>0;i--)
    	{
            if(a[i]==a[i+1]) next[i]=next[i+1];
            else next[i]=i;
        }
    }
    void dfs(int k,int last,int rest)
    {
    	int i,j;
    	if(rest==0)
    	{
    		if(k==m){bj=1;return;}
    		for(i=1;i<=tot;i++)
    		 if(!ex[i]){ex[i]=1;break;}
    		dfs(k+1,i,len-a[i]);
    		ex[i]=0;
    		if(bj)return;
    	}
    	int l=last+1,r=tot,mid;
    	while(l<r)
    	{
    		mid=l+r>>1;
    		if(a[mid]<=rest)r=mid;
    		else l=mid+1;
    	}
    	for(i=l;i<=tot;i++)
    	{
    		if(!ex[i])
    		{
    			ex[i]=1;
    			dfs(k,i,rest-a[i]);
    			ex[i]=0;
    			if(bj)return;
    			if(rest==a[i]||rest==len)return;
    			i=next[i];
    			if(i==tot)return;
    		}
    	}
    }
    void solve()
    {
    	int i,j;
    	for(int i=a[1];i<=sum/2;i++)
    	{
    		if(sum%i==0)
    		{
    			len=i;
    			ex[1]=1;
    			bj=0;
    			m=sum/i;
    			dfs(1,1,len-a[1]);
    			if(bj){cout<<len<<endl;return;}
    		}
    	}
    	cout<<sum;
    }
    int main()
    {
    	read();
    	solve();
    	return 0;
    }
    

    习题

    1. P1074 靶形数独(比较好的剪枝练习题虽然可以被生草做法过掉

    2. P1283 平板涂色(写了题解,似乎比较水?)

    3. UVA12558 埃及分数(迭代加深搜索,这里好像没讲?有时间补上)

    SECOND 广搜(bfs)

    除了深搜之外,搜索的另一利器是广搜。打个形象的比方,如果说深搜是一条路走到黑,不撞南墙不回头的话,广搜就是对每种枝条都稍微一探,如果在这一层没有找到结果,再逐级深入。

    同时因为广搜的特殊性,广搜搜到的第一个符合条件的答案往往就是最优解。

    因为广搜是利用队列进行工作的,所以判重,也就是判断这个元素有没有在队列里,是一个很重要的工作。但是有些题的判重并没有那么简单,例如此题:P2730 [USACO3.2]魔板 Magic Squares。这里的元素是一个序列,我们需要对其进行状态压缩,给它每一种不同的序列都标一个号,在判重时使用这个序号来判重。

    此题判重还需要学会康托展开,有兴趣的可以点击模板传送门自行学习,这里不会讲述。

    魔板代码也放这

    代码
    #include<bits/stdc++.h>
    using namespace std;
    int jc[10]={1,1,2,6,24,120,720,5040};
    int g,st,prt[50005],b[1000000]={0},step[50005];
    char a[50005];
    struct node{
    	int a[2][4];
    }start,goal,q[90000];
    int turn(node x)
    {
    	int i,j,res=0,t[8],s;
    	for(i=0;i<4;i++)t[i]=x.a[0][i];
    	for(i=3;i>=0;i--)t[7-i]=x.a[1][i];
    	for(int i=0;i<8;i++)
    	{
    		s=0;
    		for(j=i+1;j<=7;j++)if(t[j]<t[i])s++;
    		res+=jc[7-i]*s;
    	}
    	return res;
    }
    node change(int way,int num)
    {
    	node tep;
    	if(way==1)
    	{
    		for(int i=0;i<4;i++)tep.a[0][i]=q[num].a[1][i];
    		for(int i=0;i<4;i++)tep.a[1][i]=q[num].a[0][i];
    		return tep;
    	}
    	if(way==2)
    	{
    		tep.a[0][0]=q[num].a[0][3];
    		tep.a[1][0]=q[num].a[1][3];
    		for(int i=1;i<4;i++)tep.a[0][i]=q[num].a[0][i-1];
    		for(int i=1;i<4;i++)tep.a[1][i]=q[num].a[1][i-1];
    		return tep;
    	}
    	if(way==3)
    	{
    		tep.a[0][0]=q[num].a[0][0];tep.a[1][0]=q[num].a[1][0];
    		tep.a[0][1]=q[num].a[1][1];tep.a[1][2]=q[num].a[0][2];
    		tep.a[0][3]=q[num].a[0][3];tep.a[1][3]=q[num].a[1][3];
    		tep.a[0][2]=q[num].a[0][1];tep.a[1][1]=q[num].a[1][2];
    		return tep;
    	}
    }
    void print(int num)
    {
    	if(num==1)return;
    	print(prt[num]);
    	cout<<a[num];
    }
    void bfs()
    {
    	int op=1,cl=1,i,t;node tep;
    	q[1]=start;step[1]=0;prt[1]=1;
    	while(op<=cl)
    	{
    		for(int i=1;i<=3;i++)
    		{
    			tep=change(i,op);
    			t=turn(tep);
    			if(!b[t])
    			{
    				q[++cl]=tep;
    				step[cl]=step[op]+1;
    				b[t]=1;
    				prt[cl]=op;
    				a[cl]=char('A'+i-1);
    				if(t==g)
    				{
    					cout<<step[cl]<<endl;
    					print(cl);return;
    				}
    			}
    		}
    		op++;
    	}
    }
    int main()
    {
    	for(int i=0;i<4;i++)start.a[0][i]=i+1;
    	for(int i=3;i>=0;i--)start.a[1][i]=8-i;
    	st=turn(start);b[st]=1;
    	for(int i=0;i<4;i++)cin>>goal.a[0][i];
    	for(int i=3;i>=0;i--)cin>>goal.a[1][i];
    	g=turn(goal);
    	if(g==st)
    	{
    		cout<<0;return 0;
    	}
    	bfs();
    	return 0;
    }
    
    bfs的优化方法还有如双端队列,双向bfs等,由于博主太弱,所以这里不多说。

    bfs习题

    1. UVA1714 Keyboarding

    2. P1225 黑白棋游戏

  • 相关阅读:
    jQuery源码学习9——DOMReady加载
    jQuery源码学习8——工具方法之init
    jQuery源码学习7——实例成员
    jQuery源码学习6——工具方法之事件系统
    SQL中EXCEPT函数在 Mysql 和 sqlServer 中的替代方法
    关系型数据库及优势
    jsp小基础归纳
    eclipse换了高版本的maven插件后报错:org.apache.maven.archiver.MavenArchiver.getManifest(org.apache.maven.project
    开发常用网站收藏
    Struts2
  • 原文地址:https://www.cnblogs.com/luotao0034/p/13935520.html
Copyright © 2020-2023  润新知