第5次上机内容预测

来源:互联网 发布:聪明人思维方式知乎 编辑:程序博客网 时间:2024/06/08 10:31

口胡一段:2333说好要坚持写博客结果上一条是一个月以前,第五次上机预感会很难(大概因为第四次真的有点送分),所以先写一篇押题+部分原理讲解的博客。

主要分为:贪心——活动安排问题;DP——逆推+概率+多路;二分图匹配以及最大流问题,及其经典代表题(目标两天刷30道的节奏)有些有意思但是没什么太大关系的我也会瞎BB一部分。

因为时间仓促,难免会有疏漏之处,敬请指正!

First Blood!贪心—活动安排问题

主要分为经典活动安排;活动安排plus;最大区间调度;带权区间调度;最小区间覆盖;最大区间重叠;综合区间调度;区间选点问题几大部分

1 经典活动安排
说到贪心,先来讲一下跟其极为相似的DP的区别——两者均运用了分治思想,均考虑局部最优解。然而对于DP来说,这一步的最优解依赖于上一步(且常常仅依赖于上一步),对于贪心来说,各局部最优解之间并无关系。
活动安排问题作为经典的贪心问题,按惯例先去考虑如何取得局部最优解。广泛结论是:按照活动最早结束时间为原则解决问题。在此有两种常规方法:
   1  将活动开始时间与结束时间两个数组分别进行非递减排序。记录最初最早结束时间为结束时间数组的第一个数值,遍历开始时间数组,若满足开始时间大于等于当下记录的最早结束时间,则更新最早结束时间。
   2  另一种更为简洁的做法:只对结束时间进行非递减排序。遍历结束时间数组,若有开始时间满足大于等于当下结束时间,更新最早结束时间。
第2种方法代码:
#include <bits/stdc++.h>struct node{int start;int end;int flag;};bool cmp(node x,node y)  {      return x.end<y.end;  }int main(){int n;while(scanf("%d",&n)!=EOF) //number of activity{node a[n+2];for(int i=0;i<n;i++){scanf("%d%d",&a[i].start,&a[i].end);a[i].flag=0;}std::sort(a,a+n,cmp); int sum=0,lastend=0;if(a[0].end!=0){lastend=a[0].end;sum++;a[0].flag=1;}for(int i=1;i<n;i++){if(a[i].start>=lastend){lastend=a[i].end;sum++;a[i].flag=1;}}printf("%d\n",sum);printf("所取活动分别为:\n");for(int i=0;i<n;i++){if(a[i].flag==1)printf("(%d,%d)\n",a[i].start,a[i].end);}}}

经典例题:HDU2037 今年暑假不AC  板子题,就不上代码了。(HDU编译不了bits/stdc++.h)

2 活动安排plus

有若干个活动,第i个开始时间和结束时间是(S,E),活动之间不能交叠,要把活动都安排完,至少需要几个教室?

与经典不同:要求所有活动均被安排,决定教室个数的是开始时间与结束时间的冲突个数。故在此问题中,需要遍历开始时间数组,若大于等于该教室最早结束时间,并入教室,否则单开教室,所有存在教室均应被遍历。

#include <iostream>#include <cstring>#include <algorithm> using namespace std;struct node{int start;int end;};bool cmp(node x,node y)  {      return x.start<y.start;  }int main(){int n;while(cin>>n){node a[n+2];for(int i=0;i<n;i++){cin>>a[i].start>>a[i].end;}sort(a,a+n,cmp); int classroom[n+2];//该数组下标记录有几个教室,数值记录该教师最早结束时间memset(classroom,0,sizeof(classroom));int number=0,mark=0;classroom[0]=a[0].end;for(int i=1;i<n;i++){mark=0;for(int j=0;j<=number;j++){if(a[i].start>=classroom[j]){classroom[j]=a[i].end;mark=1;break;}}if (mark == 0) {            number++;             classroom[number]=a[i].end;         }  } cout<<number+1<<endl;}}


Double Kill!DP问题

主要分为逆推DP(个人喜欢叫期望DP)和概率DP两部分

概率DP和逆推DP的区别:一般地,求概率是正推、求期望是逆推。因为若是求概率,一般基本事件的概率都是确定的,可以采用正推;而若是期望,每一步都是不可预测的,需要从结尾逆推回起点。

不多说,直接上题

1 期望DP:
HDU 3853 练手题 不在此处赘述
HDU 4856 相似3853 没什么坑点 不赘述
POJ 2096
有些难理解的题,不过分析清楚DP递推关系就好了。为什么会想到使用DP呢?因为当下的结果与且仅与前一天密切相关(而且让求了期望233),理解之后本题与HDU 3853是非常相似的,不过有一个小区别需要搞清楚:HDU 3853的结束状态在(1,1),而此题结束状态在(0,0)。为什么呢?这与数组及其下标表示的意义有关。在前一道题中,dp[i][j]表示的是从点(i,j)出发到终点的期望,与之相似,后一道题的dp[i][j]表示的是从发现了i个种类bug,存在于j个子系统中,到发现n种bug,存在于s个子系统中的期望(如果难以理解的话,可以将它与前一道题的意义进行类比)。那么可以看出,从正序的角度来说,第一道题的起始状态在(1,1),而第二道题在0个bug,0个子系统。并且!永远注意逆推DP中关于目标状态设零的判断。
#include <iostream>#include <cstring>using namespace std;double dp[1003][1003];//dp[i][j];/dp[n][s] i bug j system//dp[i][j] 在已有bug的系统中发现已经被发现过的bug类型 //int p1=i*j/n*s;//dp[i+1][j] 在已有bug的系统中发现新bug类型 //int p2=(n-i)*j/n*s;//dp[i][j+1] 在新系统中发现已经被发现过的bug类型 //int p3=i*(s-j)/n*s;//dp[i+1][j+1] 在新系统中发现新bug类型 //int p4=(n-i)*(s-j)/n*s;int main(){int n,s;while(cin>>n>>s){memset(dp,0,sizeof(dp));for(int i=n;i>=0;i--){for(int j=s;j>=0;j--){if(i==n&&j==s) continue;//注意此处的条件判断!!double p1=(n*s-i*j)*1.0;double p2=(n-i)*j*1.0;double p3=i*(s-j*1.0);double p4=(n-i)*(s-j)*1.0;dp[i][j]=(p2*dp[i+1][j]+p3*dp[i][j+1]+p4*dp[i+1][j+1]+n*s)/p1;}}printf("%.4lf\n",dp[0][0]);}}

2 概率DP
POJ 3071
概率DP,采用正推方法,因为每支队伍的获胜概率在开始时是已知的。
本题是全概率问题,放在此处是想明确一下概率DP的特点:当下状态是由所有子状态共同转化而来。所以概率DP只有动态没有规划,即没有判定最佳状态的过程。
不多说了,看注释就好。
#include <iostream>#include <cstring>using namespace std;double dp[155][155],p[155][155];//do[i][j]表示在第i轮比赛中j队获胜的概率//注意初始化边界:在第0轮比赛中,每支队伍获胜的概率均为1 int main(){int n;//有几轮比赛 while(cin>>n){if(n==-1) break;int num;//队伍总数num=1<<n;memset(dp,0,sizeof(dp)); for(int i=0;i<num;++i)              dp[0][i]=1;           for(int i=0;i<num;i++)        {        for(int j=0;j<num;j++)        cin>>p[i][j];}for(int i=1;i<=n;i++){for(int j=0;j<num;j++){for(int k=0;k<num;k++){if((j>>(i-1))==((k>>(i-1))^1)) //保证j队和k队是第一次进行比赛dp[i][j]+=dp[i-1][j]*dp[i-1][k]*p[j][k];//j,k两队均需要在第i-1轮比赛中获胜,p[j][k]表示j队击败k队的概率 }}}int ans=0;for(int i=1;i<num;i++){if(dp[n][i]>dp[n][ans])  ans=i;}cout<<ans+1<<endl;}}


Triple Kill!二分图问题

二分图的定义:顶点可以分为两个集合,每一条边的端点都分别位于这两个集合。等价定义:不含有「含奇数条边的环」的图。
匹配的定义:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。
完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。


 1  无权二分图
经典问题是求无权二分图的最大匹配,一般采用匈牙利算法。
  
   二分图匹配之匈牙利算法
分为BFS和DFS两种,一般优选BFS,稠密和稀疏图的效率均可以保证,当然也是比DFS多了几行。
匈牙利算法要静下心好好理解啊!不要直接上来就怼题,这个算法真的是很神奇了。
初级讲解:http://blog.csdn.net/dark_scope/article/details/8880547
完全版讲解:http://www.renfei.org/blog/bipartite-matching.html,注意补充定理哦,感觉助教会从这里出题。

最大匹配数:最大匹配的匹配边的数目

最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择

最大独立数:选取最多的点,使任意所选两点均不相连

最小路径覆盖数:对于一个 DAG(有向无环图),选取最少条路径,使得每个顶点属于且仅属于一条路径。路径长可以为 0(即单个点)。
定理1:最大匹配数 = 最小点覆盖数(这是 Konig 定理)
定理2:最大匹配数 = 最大独立数
定理3:最小路径覆盖数 = 顶点数 - 最大匹配数

例题:POJ 3041

求最小点覆盖数,运用定理1,转化为求最大匹配数问题。板子题。

匈牙利算法求解的是最大匹配问题,还有一类完全匹配问题,实质上是判定。在无权图中只要节点数/2==匹配数即可。

补充:二分图匹配中有Hopcroft-Karp算法,基于最大流思想,比较复杂,类似于最大流中Dinic算法。是目前已知的最快的解决二分图匹配的算法。

https://www.cnblogs.com/crazyac/articles/1917278.html 真TM难啊。

可以参考1973年算法两位作者的论文:《Algorithm for Maximum Matchings in Bipartite Graphs》,发表于SIAM J. Computing

例题:buaacoding 570 二营长,你他娘的意大利炮呢(这题名太有意思了忍不住帮学长安利一发)实际上是脱胎于POJ 3041

   二分图匹配之二分图的判定

难题先略过,明白二分图问题中最主要的匹配算法的思想后,看一些相关问题。先看这个简单的判定问题,嗯我的意思是后面的那个KM算法是玄学。

判定思路:使用染色法。遍历其中一个维度的节点,与其相连的节点染色情况会有以下三种

1 未染色:将其染上与本节点不同的色即可

2 已染色但是颜色与本节点相同:非二分图

3 已染色且颜色与本节点不同:跳过该节点

可以看出算法和匈牙利算法是很相似的,不在此处粘贴代码了。

   二分图匹配之带权二分图最大匹配

说实话,不会……甩链接:https://www.cnblogs.com/kuangbin/archive/2012/08/19/2646535.html


Quadra Kill!最大流问题

算导上有完整的伪代码,翻译即可。鉴于教材上对该问题已经阐述详尽,就不多述原理了。

现行主要有4个算法:Ford-Fulkerson(效率比较低);Edmonds-Karp(基于BFS);Dinic(基于BFS);SAP(难!慎!)

为什么最难的最大流东西这么少:因为真的写不完这篇博客了啊啊马上就要上机了还没刷题


Penda Kill!助教的凝视

我知道有好多东西没写完……这周上机顺利的话会尽量补上的……
就算看完了每一个算法打了无数的代码,对于我来说上机也不可能全A的233,大佬之路遥遥无期,先膜为敬
上机快乐~顺颂学祺!

原创粉丝点击