一、区间调度问题
有n项工作,每项工作分别在a[i].begin开始,在a[i].end结束,求最多可参加的任务数。
有三种可选方案:
1.每次都选取结束时间最早的工作。
2.每次都选取用时最短的工作。
3.每次都选取与最少可选工作有重叠的工作。
实验证明:第一个是正确的,其他两种都可找到反例。
证明:
显然,该算法最后选出的区间不互相重叠,下面证明所选出区间的数量是最多的。设fi为该算法所接受的第i个区间的右端点坐标,gi为某最优解中的第i个区间的右端点坐标。
命题1.1 当i>=1时,该算法所接受的第i个区间的右端点坐标fi<=某最优解中的第i个区间的右端点坐标gi。
该命题可以运用数学归纳法来证明。对于i=1,命题显然为真,因为算法第一个选择的区间拥有最小右端点坐标。令i>1,假定论断对i-1为真,即fi-1<=gi-1。则最优解的第i个可选区间所组成的集合包含于执行该算法时第i个可选区间所组成的集合;而当算法选择第i个区间时,选的是在可选区间中右端点坐标最小的一个,所以有fi<=gi。证毕。
设该算法选出了k个区间,而最优解选出了m个区间。
命题1.2 最优解选出的区间数量m=该算法选出的区间数量k。
假设m>k,根据命题1.1,有fk<=gk。由于m>k,必然存在某区间,在gk之后开始,故也在fk之后开始。而该算法一定不会在选了第k个区间后停止,还会选择更多的区间,产生矛盾。所以m<=k,又因为m是最优解选出区间个数,所以m=k。
综上所述,算法选出的区间是最优解。
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 int main() 8 { 9 int n; 10 char s[2010]; 11 cin >> n; 12 for (int i = 1; i <= n; i++) 13 cin >> s[i]; 14 int a = 1, b = n; 15 int ans = 0; 16 while (a <= b) { 17 bool flag = 0; 18 for (int i = 0; a + i <= b; i++) 19 { 20 if (s[a + i] < s[b - i]) 21 { 22 flag = 1; 23 break; 24 } 25 else if (s[a + i] > s[b - i]) 26 { 27 flag = 0; 28 break; 29 } 30 } 31 if (flag == 1) { 32 ans++; 33 cout << s[a++]; 34 if (ans % 80 == 0) cout << endl; 35 } 36 else { 37 ans++; 38 cout << s[b--]; 39 if (ans % 80 == 0) cout << endl; 40 } 41 } 42 cout << endl; 43 return 0; 44 }
二、字典序最小问题
给定长度为N的字符串S,要构造一个长度为N的字符串T。有两种操作:
1.从S的头部删除一个字符,加到T的尾部
2.从S的尾部删除一个字符,加到T的尾部
目标是要构造字典序尽可能小的字符串T
方法:
反转字符串S的字符串S1
按照字典序比较S和S1
若S较小 就从头部删除
若S1较小 就从尾部删除
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 int main() 8 { 9 int n; 10 char s[2010]; 11 cin >> n; 12 for (int i = 1; i <= n; i++) 13 cin >> s[i]; 14 int a = 1, b = n; 15 int ans = 0; 16 while (a <= b) { 17 bool flag = 0; 18 for (int i = 0; a + i <= b; i++) 19 { 20 if (s[a + i] < s[b - i]) 21 { 22 flag = 1; 23 break; 24 } 25 else if (s[a + i] > s[b - i]) 26 { 27 flag = 0; 28 break; 29 } 30 } 31 if (flag == 1) { 32 ans++; 33 cout << s[a++]; 34 if (ans % 80 == 0) cout << endl; 35 } 36 else { 37 ans++; 38 cout << s[b--]; 39 if (ans % 80 == 0) cout << endl; 40 } 41 } 42 cout << endl; 43 return 0; 44 }
三、添加守卫
直线上有N个点。从这N个点中选择若干个,给他们加上标记。对每一个点,距离为R以内的区域内必须有带有标记的点。
在满足这个条件的同时,尽可能少的点添加标记。请问至少要有多少点被加上标记。
方法:
从第一个点开始,找到距离R范围内最右边的点加上标记
再从标记的点开始,找R的范围外的第一个点 作为起始点。
从起始点开始重复。
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 int main() 8 { 9 int a, b; 10 while (cin >> a >> b) { 11 if (a ==-1 && b==-1) break; 12 int s[1010],ans=0; 13 for (int i = 1; i <= b; i++) 14 cin >> s[i]; 15 sort(s + 1, s + b + 1); 16 int i = 1; 17 while (i <= b) { 18 int c = s[i++]; 19 while (i <= b && s[i] <= a + c) i++; 20 int p = s[i - 1]; 21 ans++; 22 while (i <= b && s[i] <= p + a) i++; 23 } 24 cout << ans << endl; 25 } 26 return 0; 27 }
四、木板切割
把一块长木板切割成N块,每次切割木板时,需要的开销为这块木板的长度。
求出将木板切割完最小的开销。
输入 :
3
8 5 8
输出:
34(8+5+13+8)//另一种是(8+8+16+5)不是最小舍去。
方法:
由于每次切木板都是切成两部分,可以参照二叉树。
木板长度*节点的深度。
对于最优解来说:最短的板和次短的板应是兄弟节点,最短的板也应该是深度最大的叶子节点。
在数组中找最小的和次小的,答案加上这两个数,再把这两个数合并起来的一个大数插入数组中(这两个数已经无用),总数-1.
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 int main() 8 { 9 int n; 10 long long ans = 0;//long long 11 int a[20010]; 12 cin >> n; 13 for (int i = 1; i <= n; i++) 14 cin >> a[i]; 15 while (n > 1) { 16 int min1 = 1, min2 = 2; 17 if (a[min1] > a[min2]) swap(min1, min2); 18 for (int i = 3; i <= n; i++) 19 { 20 if (a[i] < a[min1]) { 21 min2 = min1; 22 min1 = i; 23 } 24 else if (a[i] < a[min2]) { 25 min2 = i; 26 } 27 } 28 int t = a[min1] + a[min2]; 29 ans += t; 30 if (min1 == n) swap(min1, min2);//若a[n-1]是最小的数,就可以不要。 31 a[min1] = t; 32 a[min2] = a[n]; 33 n--; 34 } 35 cout << ans << endl; 36 return 0; 37 }