本系列是这本算法教材的扩展:《算法竞赛入门到进阶》(京东 当当) 清华大学出版社 PDF下载地址:https://github.com/luoyongjun999/code 其中的“补充资料” 若有建议,请联系:(1)QQ 群,567554289;(2)做者QQ,15512356php
[toc]html
二分法和三分法是算法竞赛中常见的算法思路,本文介绍了它们的理论背景、模板代码、典型题目。c++
在《计算方法》教材中,关于非线性方程的求根问题,有一种是二分法。 方程求根是常见的数学问题,知足方程: $f(x) = 0$ (1-1) 的数$x'$称为方程(1-1)的根。 所谓非线性方程,是指$f(x)$中含有三角函数、指数函数或其余超越函数。这种方程,很难或者没法求得精确解。不过,在实际应用中,只要获得知足必定精度要求的近似解就能够了,此时,须要考虑2个问题: (1)根的存在性。用这个定理断定:设函数在闭区间$[a, b]$上连续,且$f(a) ∙ f(b) < 0$,则$f(x) = 0$存在根。 (2)求根。通常有两种方法:搜索法、二分法。 搜索法:把区间$[a, b]$分红$n$等份,每一个子区间长度是∆x,计算点$x_k = a + k∆x$, $(k=0,1,2,3,4,...,n)$的函数值$f(x_k)$,若$f(x_k) = 0$,则是一个实根,若相邻两点知足$f(x_k) ∙ f(x_{k+1}) < 0$,则在$(x_k, x_{k+1})$内至少有一个实根,能够取($x_k+ x_{k+1})/2$为近似根。 二分法:若是肯定$f(x)$在区间$[a, b]$内连续,且$f(a) ∙ f(b) < 0$,则至少有一个实根。二分法的操做,就是把$[a, b]$逐次分半,检查每次分半后区间两端点函数值符号的变化,肯定有根的区间。 什么状况下用二分?两个条件:<font color=#FF0033>上下界$[a, b]$肯定、函数在$[a, b]$内单调</font>。git
<div align=center><img src="https://img2020.cnblogs.com/blog/1954999/202003/1954999-20200304125222269-1547842922.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkxNDU5Mw==,size_16,color_FFFFFF,t_70" width="30%"></div> <center>图1.1 单调函数</center>github
复杂度:通过n次二分后,区间会缩小到$(b - a)/2^n$。给定$a$、$b$和精度要求$ε$,能够算出二分次数$n$,即知足$(b - a)/2^n <ε$。因此,二分法的复杂度是$O(logn)$的。例如,若是函数在区间$[0, 100000]$内单调变化,要求根的精度是$10^{-8}$,那么二分次数是44次。 二分很是高效。因此,若是问题是单调性的,且求解精确解的难度很高,能够考虑用二分法。 在算法竞赛题目中,有两种题型:整数二分、实数二分。整数域上的二分,注意终止边界、左右区间的开闭状况,避免漏掉答案或者死循环。实数域上的二分,须要注意精度问题。算法
先看一个简单问题:在有序数列a[]中查找某个数x;若是数列中没有x,找它的后继。经过这个问题,给出二分法的基本代码。 若是有x,找第一个x的位置;若是没有x,找比x大的第一个数的位置。数组
<div align=center><img src="https://img2020.cnblogs.com/blog/1954999/202003/1954999-20200304111020679-183077438.jpg" width="60%"></div> <center> 图2.1 (a)数组中有x (b)数组中没有x </center>数据结构
示例:a[] = {-12,-6,-4,3,5,5,8,9},其中有n = 8个数,存储在a[0]~a[7]。 1)查找x = -5,返回位置2,指向a[2] = -4; 2)查找x = 7,返回位置6,指向a[6] = 8; 3)特别地,若是x 大于最大的a[7] = 9,例如x = 12,返回位置8。因为不存在a[8],因此此时是越界的。 下面是模板代码。函数
<center>查找大于等于 x的最小的一个的位置(x或者x的后继)</center> ```c int bin_search(int *a, int n, int x){ //a[0]~a[n-1]是单调递增的 int left = 0, right = n; //注意:不是 n-1 while (left < right) { int mid = left + (right-left)/2; //int mid = (left + right) >> 1; if (a[mid] >= x) right = mid; else left = mid + 1; } //终止于left = right return left; //特殊状况:a[n-1] < x时,返回n } ```学习
下面对上述代码进行补充说明: (1)代码执行完毕后,left==right,二者相等,即答案所处的位置。 (2)复杂度:每次把搜索的范围缩小一半,总次数是log(n)。 (3)中间值mid 中间值写成mid = left + (right-left)/2 或者mid = (left + right) >> 1都行 [参考李煜东《算法竞赛进阶指南》26页,有mid = (left + right) >> 1的细节解释]。不过,若是left + right很大,可能溢出,用前一种更好。 不能写成 mid = (left + right)/2; 在有负数的状况下,会出错。 (4)对比测试 bin_search()和STL的lower_bound()的功能是同样的。下面的测试代码,比较了bin_search()和lower_bound()的输出,以此证实bin_search()的正确性。注意,当a[n-1]<key时,lower_bound()返回的也是n。 代码执行如下步骤: 1)生成随机数组a[]; 2)用sort()排序; 3)生成一个随机的x; 4)分别用bin_search()和lower_bound()在a[]中找x; 5)比较它们的返回值是否相同。
<center>bin_search()和lower_bound()对比测试</center>
#include<bits/stdc++.h> using namespace std; #define MAX 100 //试试10000000 #define MIN -100 int a[MAX]; //若是MAX超过100万,大数组a[MAX]最好定义为全局。 //大数组定义在全局的缘由是:有的评测环境,栈空间很小,大数组定义在局部占用了栈空间致使爆栈。 //如今各大OJ和比赛都会设置编译命令使栈空间等于内存大小,不会出现爆栈。 unsigned long ulrand(){ //生成一个大随机数 return ( (((unsigned long)rand()<<24)& 0xFF000000ul) |(((unsigned long)rand()<<12)& 0x00FFF000ul) |(((unsigned long)rand()) & 0x00000FFFul)); } int bin_search(int *a, int n, int x){ //a[0]~a[n-1]是有序的 int left = 0, right = n; //不是 n-1 while (left < right) { int mid = left+(right-left)/2; //int mid = (left+ right)>>1; if (a[mid] >= x) right = mid; else left = mid + 1; } return left; //特殊状况:若是最后的a[n-1] < key,left = n } int main(){ int n = MAX; srand(time(0)); while(1){ for(int i=0; i< n; i++) //产生[MIN, MAX]内的随机数,有正有负 a[i] = ulrand() % (MAX-MIN + 1) + MIN; sort(a, a + n ); //排序,a[0]~a[n-1] int test = ulrand() % (MAX-MIN + 1) + MIN; //产生一个随机的x int ans = bin_search(a,n,test); int pos = lower_bound(a,a+n,test)-a; //比较bin_search()和lower_bound()的输出是否一致: if(ans == pos) cout << "!"; //正确 else { cout << "wrong"; break;} //有错,退出 } }
若是只是简单地找x或x附近的数,就用STL的lower_bound()和upper_bound()函数。有如下状况: (1)查找第一个大于x的元素的位置:upper_bound()。代码例如: pos = upper_bound(a, a+n, test) - a; (2)查找第一个等于或者大于x的元素:lower_bound()。 (3)查找第一个与x相等的元素:lower_bound()且 = x。 (4)查找最后一个与x相等的元素:upper_bound()的前一个且 = x。 (5)查找最后一个等于或者小于x的元素:upper_bound()的前一个。 (6)查找最后一个小于x的元素:lower_bound()的前一个。 (7)单调序列中数x的个数:upper_bound() - lower_bound()。
寻找指定和的整数对。这是一个很是直接的二分法问题。 ∎问题描述 输入n ( n≤100,000)个整数,找出其中的两个数,它们之和等于整数m(假定确定有解)。题中全部整数都能用int 表示。 ∎题解 下面给出三种方法: (1)暴力搜,用两重循环,枚举全部的取数方法,复杂度$O(n^2)$。超时。 (2)二分法。首先对数组从小到大排序,复杂度$O(nlogn)$;而后,从头至尾处理数组中的每一个元素a[i],在a[i]后面的数中二分查找是否存在一个等于 m - a[i]的数,复杂度也是$O(nlogn)$。两部分相加,总复杂度仍然是$O(nlogn)$。 (3)<font color=#FF0033>尺取法</font>/双指针/two pointers。对于这个特定问题,更好的、标准的算法是:首先对数组从小到大排序;而后,设置两个变量L和R,分别指向头和尾,L初值是0,R初值是n-1,检查$a[L]+a[R]$,若是大于m,就让R减1,若是小于m,就让L加1,直至$a[L]+a[R] = m$。排序复杂度$O(nlogn)$,检查的复杂度$O(n)$,总复杂度$O(nlogn)$。检查的代码这样写:
void find_sum(int a[], int n, int m){ sort(a, a + n - 1); //先排序 int L = 0, R = n - 1; //L指向头,R指向尾 while (L < R){ int sum = a[L] + a[R]; if (sum > m) R--; if (sum < m) L++; if (sum == m){ cout << a[L] << " " << a[R] << endl; //打印一种状况 L++; //可能有多种状况,继续 } } }
上面给出的二分法代码bin_search(),处理的是简单的数组查找问题。从这个例子,咱们能学习到二分法的思想。 在用二分法的典型题目中,主要是用二分法思想来进行断定。它的基本形式是:
while (left < right) { int ans; //记录答案 int mid = left+(right-left)/2; //二分 if (check(mid)){ //检查条件,若是成立 ans = mid; //记录答案 … //移动left(或right) } else … //移动right(或left) }
因此,二分法的难点在于如何建模和check()条件,其中可能会套用其余算法或者数据结构。 二分法的典型应用有:<font color=#FF0033>最小化最大值、最大化最小值。</font>
这是典型的最大值最小化问题。 ∎题目描述 例如,有一个序列{2,2,3,4,5,1},将其划分红3个连续的子序列S(1)、S(2)、S(3),每一个子序列最少有一个元素,要求使得每一个子序列的和的最大值最小。 下面举例2个分法: 分法1:S(1)、S(2)、S(3)分别是(2,2,3)、(4,5)、(1),子序列和分别是七、九、1,最大值是9; 分法2:(2,2,3)、(4)、(5,1),子序列和是七、四、6,最大值是7。 分法2更好。 ∎题解 在一次划分中,考虑一个x,使x知足:对任意的S(i),都有S(i)<=x,也就是说,x是全部S(i)中的最大值。题目须要求的就是找到这个最小的x。这就是<font color=#FF0033>最大值最小化</font>。 如何找到这个x?从小到大一个个地试,就能找到那个最小的x。 简单的办法是:枚举每个x,用贪心法每次从左向右尽可能多划分元素,S(i)不能超过x,划分的子序列个数不超过m个。这个方法虽然可行,可是枚举全部的x太浪费时间了。 改进的办法是:用二分法在[max, sum]中间查找知足条件的x,其中max是序列中最大元素,sum是全部元素的和。
“通往奥格瑞玛的道路”,来源:https://www.luogu.org/problem/P1462 ∎题目描述 给定无向图,n个点,m条双向边,每一个点有点权fi(这个点的过路费),有边权ci(这条路的血量)。求起点1到终点N的全部可能路径中,在总边权(总血量)不超过给定的b的前提下,所通过的路径中最大点权(这条路径上过路费最大的那个点)的最小值是多少。 题目数据:n≤10000,m≤50000,fi,ci,B≤1e9。 ∎题解 对点权fi进行二分,用dijkstra求最短路,检验总边权是否小于b。二分法是最小化最大值问题。 这一题是二分法和最短路算法的简单结合。 (1)对点权(过路费)二分。题目的要求是:从1到N有不少路径,其中的一个可行路径Pi,它有一个点的过路费最大,记为Fi;在全部可行路径中,找到那个有最小F的路径,输出F。解题方案是:先对全部点的fi排序,而后用二分法,找符合要求的最小的fi。二分次数log(fi)=log(1e9) < 30。 (2)在检查某个fi时,删除全部大于fi的点,在剩下的点中,求1到N的最短路,看总边权是否小于b,若是知足,这个fi是合适的(若是最短路的边权都大于b,那么其余路径的总边权就更大,确定不符合要求)。一次Dijkstra求最短路,复杂度是O(mlogn)。 总复杂度知足要求。
∎题目描述 “进击的奶牛”,来源:https://www.luogu.org/problem/P1824 在一条很长的直线上,指定n个坐标点(x1, ..., xn)。有c头牛,安排每头牛站在其中一个点(牛棚)上。这些牛喜欢打架,因此尽可能距离远一些。问最近的两头牛之间距离的最大值能够是多少。 这个题目里,全部的牛棚两两之间的距离有个最小值,题目要求使得这个最小值最大化。 ∎题解 (1)暴力法。从小到大枚举最小距离的值dis,而后检查,若是发现有一次不行,那么上次枚举的就是最大值。如何检查呢?用贪心法:第一头牛放在x1,第二头牛放在xj≥x1+dis的点xi,第三头牛放在xk≥xj+dis的点xk,等等,若是在当前最小距离下,不能放c条牛,那么这个dis就不可取。复杂度O(nc)。 (2)二分。分析从小到大检查dis的过程,发现能够用二分的方法找这个dis。这个dis符合二分法:它有上下边界、它是单调递增的。复杂度O(nlogn)。
#include<bits/stdc++.h> using namespace std; int n,c,x[100005];//牛棚数量,牛数量,牛棚坐标 bool check(int dis){ //当牛之间距离最小为dis时,检查牛棚够不够 int cnt=1, place=0; //第1头牛,放在第1个牛棚 for (int i = 1; i < n; ++i) //检查后面每一个牛棚 if (x[i] - x[place] >= dis){ //若是距离dis的位置有牛棚 cnt++; //又放了一头牛 place = i; //更新上一头牛的位置 } if (cnt >= c) return true; //牛棚够 else return false; //牛棚不够 } int main(){ scanf("%d%d",&n, &c); for(int i=0;i<n;i++) scanf("%d",&x[i]); sort(x,x+n); //对牛棚的坐标排序 int left=0, right=x[n-1]-x[0]; //R=1000000也行,由于是log(n)的,很快 //优化:把二分上限设置为1e9/c int ans = 0; while(left < right){ int mid = left + (right - left)/2; //二分 if(check(mid)){ //当牛之间距离最小为mid时,牛棚够不够? ans = mid; //牛棚够,先记录mid left = mid + 1; //扩大距离 } else right = mid; //牛棚不够,缩小距离 } cout << ans; //打印答案 return 0; }
实数域上的二分,比整数二分简单。 <center>实数二分的基本形式</center>
const double eps =1e-7; //精度。若是下面用for,能够不要eps while(right - left > eps){ //for(int i = 0; i<100; i++){ double mid = left+(right-left)/2; if (check(mid)) right = mid; //断定,而后继续二分 else left = mid; }
其中,循环用2种方法均可以:
while(right - left > eps) { ... } 或者: for(int i = 0; i < 100; i++) { ... }
若是用for循环,因为循环内用了二分,执行100次,至关于实现了 $1/2^100$的精度,通常比eps更精确。 for循环的100次,比while的循环次数要多。若是时间要求不是太苛刻,用for循环更简便*[参考李煜东《算法竞赛进阶指南》27页的说明]*。
∎题目描述 “Pie”,题目来源:http://poj.org/problem?id=3122 主人过生日,m我的来庆生,有n块半径不一样的圆形蛋糕,由m+1我的(加上主人)分,每人的蛋糕必须同样重,并且是一整块(不能是几个蛋糕碎块,也就是说,每一个人的蛋糕都是从一块圆蛋糕中切下来的完整一块)。问每一个人能分到的最大蛋糕是多大。 ∎题解 最小值最大化问题。设每人能分到的蛋糕大小是x,用二分法枚举x。 <center>“Pie”题的代码</center>
#include<stdio.h> #include<math.h> double PI = acos(-1.0); //3.141592653589793; #define eps 1e-5 double area[10010]; int n,m; bool check(double mid){ int sum = 0; for(int i=0;i<n;i++) //把每一个圆蛋糕都按大小mid分开。统计总数 sum += (int)(area[i] / mid); if(sum >= m) return true; //最后看总数够不够m个 else return false; } int main(){ int T; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); m++; double maxx = 0; for(int i=0;i<n;i++){ int r; scanf("%d",&r); area[i] = PI*r*r; if(maxx < area[i]) maxx = area[i]; //最大的一块蛋糕 } double left = 0, right = maxx; for(int i = 0; i<100; i++){ //while((right-left) > eps) { //for或者while都行 double mid = left+(right-left)/2; if(check(mid)) left = mid; //每人能分到mid大小的蛋糕 else right = mid; //不够分到mid大小的蛋糕 } printf("%.4f\n",left); // 打印right也对 } return 0; }
饥饿的奶牛 https://www.luogu.org/problem/P1868 寻找段落 https://www.luogu.org/problem/P1419 小车问题 https://www.luogu.org/problem/P1258
借教室 https://www.luogu.org/problem/P1083
跳石头 https://www.luogu.org/problem/P2678 聪明的质监员 https://www.luogu.org/problem/P1314 分梨子 https://www.luogu.org/problem/P1493
第k大 http://acm.hdu.edu.cn/showproblem.php?pid=6231
三分法求单峰(或者单谷)的极值,是二分法的一个简单扩展。 单峰函数和单谷函数以下图,函数f(x)在区间[l, r]内,只有一个极值v,在极值点两边,函数是单调变化的。以单峰函数为例,在v的左边,函数是严格单调递增的,在v右边是严格单调递减的。
<div align=center><img src="https://img2020.cnblogs.com/blog/1954999/202003/1954999-20200304125834578-73426390.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkxNDU5Mw==,size_16,color_FFFFFF,t_70" width="70%"></div> <center>图6.1 (1)单峰函数 (2)单谷函数</center>
下面的讲解都以求单峰极值为例。 如何求单峰函数最大值的近似值?虽然不能直接用二分法,不过,只要稍微变形一下,就能用了。 在[l, r]上任取2个点,mid1和mid2,把函数分红三段。有如下状况: (1)若f(mid1) < f(mid2),极值点v必定在mid1的右侧。此时,mid1和mid2要么都在v的左侧,要么分别在v的两侧。以下图所示。
<div align=center><img src="https://img2020.cnblogs.com/blog/1954999/202003/1954999-20200304125932965-1232705578.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkxNDU5Mw==,size_16,color_FFFFFF,t_70" width="60%"></div> <center>图6.2 状况(1):极值点v在mid1右侧</center>
下一步,令l = mid1,区间从[l, r]缩小为[mid1, r],而后再继续把它分红三段。 (2)同理,若f(mid1) > f(mid2),极值点v必定在mid2的左侧。以下图所示。下一步,令 r = mid2,区间从[l, r]缩小为[l, mid2]。
<div align=center><img src="https://img2020.cnblogs.com/blog/1954999/202003/1954999-20200304130022831-1321455925.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkxNDU5Mw==,size_16,color_FFFFFF,t_70" width="60%"></div> <center>图6.3 状况(2):极值点v在mid1右侧</center>
不断缩小区间,就能使得区间[l, r]不断逼近v,从而获得近似值。 如何取mid1和mid2?有2种基本方法: (1)三等分:mid1和mid2为[l, r]的三等分点。那么区间每次能够减小三分之一。 (2)近似三等分:计算[l, r]中间点mid = (l + r) / 2,然让mid1和mid2很是接近mid,例如mid1 = mid - eps,mid2 = mid + eps,其中eps是一个很小的值。那么区间每次能够减小接近一半。 方法(2)比方法(1)要稍微快一点。 (<font color=#FF0033>有网友说</font>不要用方法(2):“由于在有些状况下这个 eps 太小可能致使这两个算出来的相等,若是相等就有可能会判断错方向,因此其实不建议这么写,log3 和 log2 本质上是同样的。”) 注意:单峰函数的左右两边要严格单调,不然,可能在一边有f(mid1) == f(mid2),致使没法判断如何缩小区间。
下面用一个模板题给出实数三分的两种实现方法。 ∎题目描述 “模板三分法”,来源:https://www.luogu.com.cn/problem/P3382
给出一个N次函数,保证在范围[l, r]内存在一点x,使得[l, x]上单调增,[x, r]上单调减。试求出x的值。 ∎题解 下面分别用前面提到的2种方法实现:(1)三等分;(2)近似三等分。 (1)三等分
<center>mid1和mid2为[l, r]的三等分点</center>
#include<bits/stdc++.h> using namespace std; const double eps = 1e-6; int n; double a[15]; double f(double x){ //计算函数值 double s=0; for(int i=n;i>=0;i--) //注意函数求值的写法 s = s*x + a[i]; return s; } int main(){ double L,R; scanf("%d%lf%lf",&n,&L,&R); for(int i=n;i>=0;i--) scanf("%lf",&a[i]); while(R-L > eps){ // for(int i = 0; i<100; i++){ //用for也行 double k =(R-L)/3.0; double mid1 = L+k, mid2 = R-k; if(f(mid1) > f(mid2)) R = mid2; else L = mid1; } printf("%.5f\n",L); return 0; }
(2)近似三等分
<center>mid1和mid2在[l, r]的中间点附近</center>
#include<bits/stdc++.h> using namespace std; const double eps = 1e-6; int n; double a[15]; double f(double x){ double s=0; for(int i=n;i>=0;i--) s=s*x+a[i]; return s; } int main(){ double L,R; scanf("%d%lf%lf",&n,&L,&R); for(int i=n;i>=0;i--) scanf("%lf",&a[i]); while(R-L > eps){ // for(int i = 0; i<100; i++){ //用for也行 double mid = L+(R-L)/2; if(f(mid - eps) > f(mid)) R = mid; else L = mid; } printf("%.5f\n",L); return 0; }
(1)“三分求极值”,题目来源:http://hihocoder.com/problemset/problem/1142 ∎题目描述:在直角坐标系中有一条抛物线y = ax^2 + bx + c和一个点P(x, y),求点P到抛物线的最短距离d。 ∎题解:直接求距离很麻烦。观察这一题的距离D,发现它知足单谷函数的特征,用三分法很合适。 (2)<font color=#FF0033>三分套三分</font>,是计算几何的常见题型。 “Line belt”,题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=3400 ∎题目描述:给定两条线段AB、CD ,一我的在AB上跑的时候速度是p,在CD上速度是q,在其余地方跑速度是r。问从A点到D点最少的时间。 ∎题解:从A出发,先走到AB上一点X,而后走到CD上一点Y,最后到D。时间是: time = |AX|/p + |XY|/r + |YD|/q 假设已经肯定了X,那么目标就是在CD上找一点Y,使|XY|/r + |YD|/q最小,这是个单峰函数。三分套三分就能够了。
整数三分的形式是:
while (left < right) { int mid1 = left + (right - left)/3; int mid2 = right- (right - left)/3; if(check(mid1) > check(mid2)) … //移动right else … //移动left }
下面是一个例题。 ∎题目描述 “期末考试”,题目来源:https://www.lydsy.com/JudgeOnline/problem.php?id=4868 有n位同窗,每位同窗都参加了所有的m门课程的期末考试,都在焦急的等待成绩的公布。第i位同窗但愿在第ti天或以前得知全部课程的成绩。若是在第ti天,有至少一门课程的成绩没有公布,他就会等待最后公布成绩的课程公布成绩,每等待一天就会产生C不愉快度。对于第i门课程,按照本来的计划,会在第bi天公布成绩。有以下两种操做能够调整公布成绩的时间:1.将负责课程X的部分老师调整到课程Y,调整以后公布课程X成绩的时间推迟一天,公布课程Y成绩的时间提早一天;每次操做产生A不愉快度。2.增长一部分老师负责学科Z,这将致使学科Z的出成绩时间提早一天;每次操做产生B不愉快度。上面两种操做中的参数X,Y,Z都可任意指定,每种操做都可以执行屡次,每次执行时均可以从新指定参数。如今但愿你经过合理的操做,使得最后总的不愉快度之和最小,输出最小的不愉快度之和便可。 ∎题解 不愉快度是一个下凹的函数,用三分法。代码略。