700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 算法设计与分析(整理)

算法设计与分析(整理)

时间:2021-05-08 10:34:29

相关推荐

算法设计与分析(整理)

春-算法课

我与作业题库的爱恨情仇

我可没记住这个简朴的提交网站:http://47.99.179.148/

有一个很蠢的坦白,我以为这个简陋的网站,提交时不会允许我们用algorithm库,所有排序我都复制一遍快排,所以当我发现可以用时,之前的冗长代码也没有继续管他,所以看上去比较蠢。

文章目录

春-算法课我与作业题库的爱恨情仇【几个系列题】1007 最大乘积 1026 插入乘号1008 拦截导弹21025 最长非降子序列【分治思想】1010 二分搜索1016 两元素和1011 最近点对1012 寻找凸包【动态规划】(我觉得挺难的唔)1020 矩阵连乘1022 最长公共子序列1027 带权活动选择1034 树上着色1042 最低票价1043 鸡蛋掉落【神奇的思路】(小姑凉想不到,麻麻咪呀)1013 逆序列1017 电路布线1014 最大子数组和1023 穷游?“穷”游 ?1024 最优二叉搜索树【背包问题】(多彩的背包啊。) 1019 0/1背包问题1 (最简单的0-1背包) 1018 0/1背包问题2(恰好被装满才可带走的背包)1021 钢条切割【这些经典】1028 dijkstra算法1029 最小生成树1036 KMP水题各色排序1003 冒泡1004 归并1005 快排1006 堆排【贪心算法】1030 黑白连线1031 基地布置1032 岛国难题1033 机器作业1035 生产安排【网络流】 1037 非诚勿扰第一期(匈牙利算法1038 非诚勿扰第二期(KM算法)【其他】1015天际轮廓1039 拓扑排序1040 搜索二维矩阵1041 寻找两个正序数组的中位数

【几个系列题】

1007 最大乘积

Time Limit: 1000 MSMemory Limit: 1000 KB

Description

设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积能够为最大。

同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子:

有一个数字串:312, 当N=3,K=1时会有以下两种分法:

3*12=3631*2=62

这时,符合题目要求的结果是:31*2=62

现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。

Input

第一行输入M表示包含M组测试数据,每组输入有一行包含两个自然数N,K(6≤N≤40,1≤K≤6),以及一个长度为N的数字串。

Output

对于每组输入数据,输出所求得的最大乘积(一个自然数),每组一行。

Sample Input

2

6 1 101010

9 4 321044105

Sample Output

10100

5166000

这题我愣是记住了它的题号1007,我还长江七号呢,也可能是自己傻里巴巴的,longlong可以解决的,还以为要高精,记得那天晚上弄到凌晨一点还没弄好,不过有天突然发现自己longlong其实有个笨蛋错误(清零没放在每组数据循环里,给整循环外面了,就完球了)

#include<iostream>用long long using namespace std;int main(){int m, n, k;//长度为n的数字串,使用k个乘号cin >> m;long long max[40][7], data[40][40];//max[i][j]表示从第0个数字到第i个数字中插入j个乘号时所得到的最大值,//data[i][j]表示截取第i个数字到第j个数字所得到的数值。//memset(max, 0, sizeof(max));for (int i = 0; i < m; i++){for (int i = 0; i < 40; i++)for (int j = 0; j < 7; j++)max[i][j] = 0;cin >> n;cin >> k;char* a = new char[n];for (int j = 0; j < n; j++)cin >> a[j];for (int j = 0; j < n; j++)//将字符串转换为data{long long temp = 0;for (int t = j; t < n; t++){temp = temp * 10 + a[t] - '0';data[j][t] = temp;}}for (int j = 0; j < n; j++)//初始化max[j][0]即用0个乘号时的结果max[j][0] = data[0][j];for (int j = 0; j < n; j++)for (int w = 1; w <= k && w <= j; w++)for (int t = 0; t < j; t++)//max[j][w] = (max[t][w - 1] * data[t + 1][j] > max[j][w] ?max[t][w - 1] * data[t + 1][j] : max[j][w]);cout << max[n - 1][k] << endl;}return 0;}

下面这题和上面这题异曲同工,可以说是雷同。不过在之前得先明确a×(b+c)×d>=(a×b+c)×d,显然a,b,c都1-9的情况下a×c>=c是一定成立的,所以这道插入乘号以及加号的题目要使得最终结果最大,只需考虑插入乘号的位置,乘号与乘号之间必然是括号括起来的加法,所以该题就等同于上面一题。

1026 插入乘号

Time Limit: 1000 MS Memory Limit: 1000 KB

Description

给出N个1-9的数字 (v1,v2,…,vN), 不改变它们的相对位置, 在中间加入K个乘号和N-K-1个加号, 括号随便加,

使最终结果最大。因为乘号和加号一共就是N-1个,所以恰好每两个相邻数字之间都有一个符号。

例如: N=5, K=2,5个数字分别为1、2、3、4、5,可以进行如下运算:

1×2×(3+4+5)=24

1×(2+3)×(4+5)=45

(1×2+3)×(4+5)=45

等等.

Input

第一行输入M(M<=10)表示有M组数据。每组数据输入两整数N和K(N<=20, K<20), 接下来输入N个1-9的数字。

Output

输出M行正整数,第i行表示第i组数据的最大结果, 你可能需要用long long类型存储结果。

Sample Input

2

5 2

1 2 3 4 5

6 3

1 2 3 4 5 6

Sample Output

120

720

#include<iostream>using namespace std;int main(){int m, n, k;//长度为n的数字串,使用k个乘号cin >> m;long long max[20][20], data[20][20];//max[i][j]表示从第0个数字到第i个数字中插入j个乘号时所得到的最大值,//data[i][j]表示截取第i个数字到第j个数字所得到的数值。//memset(max, 0, sizeof(max));for (int i = 0; i < m; i++){for (int i = 0; i < 20; i++)for (int j = 0; j < 20; j++){max[i][j] = 0; data[i][j] = 0;}cin >> n >> k;int* a = new int[n];for (int j = 0; j < n; j++)cin >> a[j];for (int j = 0; j < n; j++){long long temp = 0;for (int t = j; t < n; t++){temp = temp + a[t];data[j][t] = temp;}}for (int j = 0; j < n; j++)max[j][0] = data[0][j];for (int j = 0; j < n; j++)for (int w = 1; w <= k && w <= j; w++)for (int t = 0; t < j; t++)max[j][w] = (max[t][w - 1] * data[t + 1][j] > max[j][w] ?max[t][w - 1] * data[t + 1][j] : max[j][w]);cout << max[n - 1][k] << endl;}return 0;}

下面三道题同样是异曲同工之妙,1009拦截导弹1和1008拦截导弹2无非就是题2需要再多实现一个算法设计(包含题1内容),故以下只呈现题2题干。

1008 拦截导弹2

Time Limit: 2000 MS Memory Limit: 2000 KB

Description

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

Input

第一行输入M表示包含M组测试数据,每组第一个输入N (N<100)表示后面有N个整数,表示导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数)。

Output

对于每组输入数据,第一行输出这套系统最多能拦截多少导弹,以及输出如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

Sample Input

2

7 300 250 275 252 200 138 245

7 181 205 471 782 1033 1058 1111

Sample Output

5 2

1 7

写这题时还没有培养多少算法设计的思维,看到标解时感觉好神奇。www

后来发现,要是能参透它是一个最长单调不升子序列问题和最长不下降子序列问题,就应该会容易些。

因此,下面的题三有关最长非降子序列的题目就是这一应用题的其中一个核心所在。

1025 最长非降子序列

Time Limit: 5000 MS Memory Limit: 5000 KB

Description

给定一个长度为N的整数数组, 请计算该数组中最长非降子序列长度。

Input

第一行输入M(M<=10)表示有M组数据。每组数据输入N(N<=10000), 接下来输入N个整数。

Output

输出M行正整数,第i行表示第i组数据的最长非降子序列长度。

Sample Input

2

4

1 3 2 4

9

4 1 7 3 2 3 5 7 6

Sample Output

3

5

关于解决最长不下降子序列

首先需要两个数组,一个数组用于存输入的整数数组数据,一个数组用于存从开头到该数据的子数组中最长非降子序列的长度,为了表示方便,我采用二维数组a[100][2],a[i][1]存数据,a[i][0]存从数组中第0个数到第i个数中最长非降子序列的长度。而至于如何求取最长非降子序列的长度,我一开始想法是采用数组前一个数a[i-1][1]与后一个数a[i][1]进行比较,如果后一个数比前一个数大,那么a[i][0]=a[i-1][0],但是这个想法是错误的,举个例子:

2 3 4 1 7

如果用这个想法a[4][0]会被赋值为2,而显然正确的值是4,

这是由于a[4][1]>a[2][1],从而a[4][0]有了一种选择赋值为a[2][0]+1的选择,

而它恰恰是正确的答案。

为了解决此,其实不难想到可以将a[i][1]与该数之前的所有数据进行比较,从而通过遍历找到其对应正确的最长非降子序列的长度。

for (in t = 0; t < i; t++)if (a[t][0] + 1 > a[i][0] && a[t][1] < a[i][1])a[i][0] = a[t][0] + 1;

关于解决最长单调不升子序列,其原理雷同于最长不下降子序列,具体实现见完整代码部分即可。

关于应用题部分,鉴于这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度,这套系统最多能拦截导弹的数量是炮弹高度数组的最长单调不升子序列的长度。

而关于如果要拦截所有导弹,最少要配备多少套这种导弹拦截系统,有想到一个方法就是:应用贪心算法,每次取最长单调不升子序列,然后在剩下的数中循环求最长单调不升子序列,直到取完为止,转化为求解的是单调不升子序列的最小划分数。恰巧我选修了组合数学,有学习到Dilworth定理:单调不升子序列的最小划分数=最长不下降子序列的长度,该问题便转换为求解最长单调不下降子序列的长度

//最长单调不升子序列&&最长不下降子序列#include<iostream>using namespace std;int main(){int m, n;cin >> m;for (int i = 0; i < m; i++){cin >> n;int a[100][2],max=1,k=0;for (int j = 0; j < n; j++){a[j][0] = 1;cin >> a[j][1];}//最长单调不升子序列for (int j = 1; j < n; j++)for (int t = 0; t <j; t++)if (a[t][0]+1>a[j][0] && a[t][1] >= a[j][1]){a[j][0] = a[t][0] + 1;if (a[j][0] > max)max = a[j][0];}//最长不下降子序列for(int j = 0; j < n; j++)a[j][0] = 1;for (int j = 1; j < n; j++)for (int t = 0; t < j; t++)if (a[t][0] + 1 > a[j][0] && a[t][1] < a[j][1])//前面的导弹高度比后面的小的个数{a[j][0] = a[t][0] + 1;if (a[j][0] > k)k = a[j][0];}cout << max << ' '<<k<< endl;}return 0;}

【分治思想】

1010 二分搜索

这个真的太经典了,话不多说,直接核心代码,通俗易懂。

(ps.是在数组已经排完序的情况下,如果你排序还有问题的话请参见1003-1006)

bool BinarySearch(int a[], int x, int low, int high){if (low > high) return false;int mid = (low + high) / 2;if (x == a[mid]) return true;else if (x > a[mid])BinarySearch(a, x, mid+1, high);else BinarySearch(a, x, low, mid-1);}

下面这题1016两元素和也是运用二分搜索就可解决,实现就不多加赘述。

1016 两元素和

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

给定一个N(N<=50000)个int型整数的集合以及一个int型整数X, 问集合中是否存在两个元素的和等于X.

Input

第一行输入M表示有M组测试. 每组测试首先输入N和X,接下来输入N个int型整数.

Output

若否存在两个元素的和等于X则输出yes, 否则输出no.

Sample Input

2

8 7

1 5 11 5 4 3 9 6

8 7

1 5 11 5 5 3 9 5

Sample Output

yes

no

【核心代码】

二分搜索+如下几行

int t = 0;for (int j = 0; j < n; j++)if (BinarySearch(a, x - a[j], 0, j - 1) || BinarySearch(a, x - a[j], j + 1, n - 1))t++;if(t!=0)cout << "yes" << endl;else cout << "no" << endl;

1011 最近点对

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

给定平面上N个点, 请找出这N个点的最近点对.

Input

第一行输入M表示包含M组测试数据,每组先输入N (N<=50000), 接着输入N个坐标(x,y), x和y均为int型整数.

Output

输出最近点对距离,精度保留2位小数

Sample Input

2

3 1 1 2 1 3 5

10 851644 996635 20388 842736 262145 615142 890041 434439 787213 89181 99282 310353 179500 803495 728862 687090 225650 604015 765534 465397

Sample Output

1.00

38153.57

首先,千万别像我一样傻巴巴的写快排,直接调algorithm库的sort函数即可,通过写一个cmp函数,用顶点的x坐标进行排序;

接着,采用分治思想将平面上的点分成左右两部分,分别进行最小点对的查找,得到最近点对距离dleft和dright,取d=min{dleft,dright};

之后还需再取中间一部分点集去进行最小点对的查找(在这里由于right-left较小,可直接采用for循环解决,反而递归调用最小点对的函数会增加时间复杂度)。

而关于中间一部分的选取,这里面涉及了组合数学中的鸽巢原理,可以证明选取距中垂线为d的宽为2d的临界区,去查找是否存在最小点对距离小于d即可。

最终,得到平面内最近点对的距离。

#include<iostream>#include<iomanip> #include<math.h>double mindistance(int,int);void QuickSort(int [], int, int);void Swap(int&, int&);using namespace std;int a[50000],b[50000];int main(){int m, n;cin >> m;for (int i = 0; i < m; i++){cin >> n;for (int j = 0; j < n; j++)cin >> a[j] >> b[j];QuickSort(a, 0, n - 1);//O(nlogn)cout <<fixed<< setprecision(2)<< mindistance(0, n - 1) << endl;}return 0;}double mindistance(int left,int right){if (left >= right)return 0x3f3f3f3f;if (left>=0&&right>=0&&right - left <= 3) {double min = 0x3f3f3f3f3f;for (int j = left; j < right ; j++){for (int k = j + 1; k <= right; k++){double d = sqrt(1.0 * (a[j] - a[k]) * (a[j] - a[k]) + 1.0 * (b[j] - b[k]) * (b[j] - b[k]));if (d < min)min = d;}}return min;}double m1= mindistance(left, (left + right) / 2);double m2= mindistance((left + right) / 2+1,right);double mi = (m1 < m2) ? m1 : m2;if ((left + right) / 2 - 6 >= left && (left + right) / 2 + 6 <= right){double m3 = 0x3f3f3f3f;for (int j = (left + right) / 2 - 6; j < (left + right) / 2 + 6; j++){for (int k = j + 1; k <= (left + right) / 2 + 6; k++){double d = sqrt(1.0 * (a[j] - a[k]) * (a[j] - a[k]) + 1.0 * (b[j] - b[k]) * (b[j] - b[k]));if (d < m3)m3 = d;}}//double m3 = mindistance((left + right) / 2 - 6, (left + right) / 2 + 6);//规模只有12,可以直接两个for循环遍历比较快,时间复杂度小点mi = (mi < m3) ? mi : m3;}return mi;}void QuickSort(int a[], int x, int y){if (x >= y)return;int pivot = a[x], pivotpos = x;for (int i = x; i <= y; i++){if (a[i] < pivot){pivotpos++;if (pivotpos != i)//小于基准的交换到左侧{Swap(a[pivotpos], a[i]);Swap(b[pivotpos], b[i]);}}}Swap(a[x], a[pivotpos]);Swap(b[x], b[pivotpos]);QuickSort(a, x, pivotpos - 1);QuickSort(a, pivotpos + 1, y);}void Swap(int& x, int& y){int temp = x;x = y;y = temp;}

1012 寻找凸包

Time Limit: 1000 MS Memory Limit: 1000 KB

Description

给定平面上N个点, 请找出这N个点的凸包.

Input

第一行输入M表示包含M组测试数据,每组先输入N (N<=100), 接着输入N个坐标(x,y), x和y均为int型整数.

Output

以最下最左点开始逆时针输出凸包, 若有多个点在同一坐标,只输出一个,若凸包上有多个点在同一线上,只输出两端点.

Sample Input

2

7 1 1 4 1 4 4 4 4 1 4 2 2 5 5

8 5 6 8 3 1 8 5 7 3 5 3 5 1 8 2 11

Sample Output

case 1:

1 1

4 1

5 5

1 4

case 2:

8 3

2 11

1 8

3 5

这题有多种解决方案,我挑选了我最能理解的一个方法进行解决,即分治法,由于懒癌晚期懒得码字,放上老师课上的PPT思路。

那么这题的关键之一就在于如何去寻找直线pdrpul最远的点,然后就想到了一个奇妙的数学方法,三角形面积最大,在底边固定的情况下,高必然达到最大,这里不就是点离直线pdrpul的距离嘛。而三角形面积这里由于知道三点的坐标,向量积方法走起,贴个他人的讲解嘻嘻。

叉乘的性质:设两向量P和Q,

P ×Q > 0 则Q在P的时针方向,

P ×Q < 0 则Q在P的时针方向,

P ×Q = 0 则Q和P共线

从而A(Xa,Ya),B(Xb,Yb),C(Xc,Yc),Area(A,B,C)=|AB×AC|/2,

Area<0,C在AB下方,Area>0,C在AB上方。

本来应该写个DealLeft和DealRight用于找到凸包每一个顶点将其用visit数组标记出来,但偷懒原罪,所谓分治,就是两个部分实现肯定很类似,就直接放在一个函数DealLeft中了,index用于记录每次找到的最远的那个点 ,if (index != -1)表示若还有点未被框到凸包中,就继续递归寻找凸包下去。

在找到所有凸包上的点后,通过visit数组将这些点存入ans二维数组,这其中点是按照x坐标排好序的,因为题目要求“以最下最左点开始逆时针输出凸包”,同时还需要判断这些点是在分割线之上还是之下,就采用上面提及的叉乘的性质,先有序输出分割线之的点,再有序输出分割线之的点,达到逆时针输出的要求。

#include<iostream>using namespace std;int x[101],y[101],visit[101],mark[101],ans[101][2];void QuickSort(int[], int, int);void Swap(int&, int&);bool cmpyx(int a_x, int a_y, int b_x, int b_y);void DealLeft(int first, int last);int Size(int p1_x, int p1_y, int p2_x, int p2_y, int p3_x, int p3_y);# define INF 0x3f3f3f3fint main(){int T, n;//y_max = -INF, y_min = INF;cin >> T;for (int i = 0; i < T; i++){cin >> n;for (int j = 0; j < n; j++){cin >> x[j] >>y[j];visit[j] = 0;}QuickSort(y,0,n-1);visit[0] = 1;visit[n - 1] = 1;DealLeft(0, n - 1); //查找上凸包;DealLeft(n - 1, 0); //查找下凸包;int t = 0;for (int i = 0; i < n; i++){if (visit[i] == 1){ans[t][0] = x[i];ans[t][1] = y[i];t++;}}cout << "case " << i + 1 << ":"<<endl;mark[0] = mark[t - 1] = 1; //数组mark避免重复检查降低效率for (int i = 1; i < t - 1; i++)mark[i] = 0;cout << ans[0][0] << " " << ans[0][1]<< endl;for (int i = 1; i < t - 1; i++){int s = Size(ans[0][0], ans[0][1], ans[t - 1][0], ans[t - 1][1], ans[i][0],ans[i][1]);if (s < 0){cout << ans[i][0] << " " << ans[i][1] << endl;mark[i] = 1;}}cout << ans[t - 1][0] << " " << ans[t - 1][1] << endl;for (int i = t-1; i > 0; i--){if (mark[i] != 1){int s = Size(ans[0][0], ans[0][1], ans[t - 1][0], ans[t - 1][1], ans[i][0], ans[i][1]);if (s > 0){cout << ans[i][0] << " " << ans[i][1] << endl;}}}}return 0;}int Size(int p1_x,int p1_y,int p2_x,int p2_y, int p3_x,int p3_y){int size = p1_x * p2_y + p3_x * p1_y + p2_x * p3_y - p3_x * p2_y - p2_x * p1_y - p1_x * p3_y;return size;}void DealLeft(int first, int last){int max = 0, index = -1;int i = first;if (first < last){for (i = first + 1; i < last; i++){int size = Size(x[first], y[first],x[i], y[i],x[last], y[last]);//if (size == 0) { visit[i] = 1; } //凸包上有多个点在同一线上,只输出两端点.if (size > max){max = size;index = i;}}}else{for (i = first-1; i > last; i--) {int size = Size(x[first], y[first], x[i], y[i], x[last], y[last]);//if (size == 0) { visit[i] = 1; } //if (size > max){max = size;index = i;}}}if (index != -1){visit[index] = 1; //对取到的点进行标注 DealLeft(first, index);DealLeft(index, last);}}void QuickSort(int y[], int a, int b){if (a >= b)return;int pivot = y[a], pivotpos = a;for (int i = a; i <= b; i++){if (y[i] < pivot){pivotpos++;if (pivotpos != i)//小于基准的交换到左侧{Swap(y[pivotpos], y[i]);Swap(x[pivotpos], x[i]);}}}Swap(y[a], y[pivotpos]);Swap(x[a], x[pivotpos]);QuickSort(y, a, pivotpos - 1);QuickSort(y, pivotpos + 1, b);}void Swap(int& x, int& y){int temp = x;x = y;y = temp;}

【动态规划】(我觉得挺难的唔)

1020 矩阵连乘

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

两个矩阵A(r行s列)和B(s行t列)相乘, 乘法代价为rst. 现给定N (N<=500)个矩阵连乘问题, 请计算最小乘法代价。

Input

第一行输入M(M<=10)表示有M组数据。每组数据第一行输入N,表示矩阵个数;接下来一行输入N个矩阵的行数和列数。

Output

输出M行正整数,第i行表示第i组数据的最小乘法代价。

Sample Input

2

3

1 2 2 3 3 4

3

4 3 3 2 2 1

Sample Output

18

18

本想这样一题显然归在分治一栏中,利用递归(如下所示)即可解决,但tle如期而至。

int solve(int a[][2], int left, int right){if (left == right)return 0;int min_num = 0x3f3f3f3f;for (int k = left; k < right; k++){int temp = solve(a, left, k) + solve(a, k + 1, right)+ a[left][0] * a[k][1] * a[right][1];if (temp < min_num)min_num = temp;}return min_num;}

那就dei另辟蹊径:用迭代同递归的思维解决。其实核心是动态规划,但是我个人对dp真的一点不敏感(好苦恼)。

s[i][j]:代表从矩阵Ai,Ai+1,Ai+2…直到矩阵Aj最小的相乘次数,

L:遍历的范围,其实也是动态规划中子问题的规模,

按照递增的方式逐步填写子问题的解,也就是先计算长度为2的所有矩阵链的解,然后计算长度3的矩阵链,直到长度n。

s[0][n-1]即为所求。

#include<iostream>int solve(int[][2], int, int);using namespace std;int a[500][2]; int s[500][500];int main(){int m, n;cin >> m;for (int x = 0; x < m; x++){cin >> n;for (int j = 0; j < n; j++)cin >> a[j][0]>>a[j][1];//cout<<solve(a, 0, n - 1)<<endl;for (int L = 2; L <= n; L++) {//遍历的范围,逐渐缩小范围,从第一个开始。for (int i = 0; i < n - L + 1; i++) {int j = i + L - 1;//每次区间加1,遵从自底向上的递归形式求解。s[i][j] = 0x3f3f3f3f;//初始化min[i,j]的值为无限大。for (int k = i; k <= j - 1; k++) {int q = s[i][k] + s[k + 1][j] + a[i][0] * a[k][1] * a[j][1];//递归求解公式if (q < s[i][j]) s[i][j] = q;}}}cout << s[0][n - 1] << endl;}return 0;}int solve(int a[][2], int left, int right){if (left == right)return 0;int min_num = 0x3f3f3f3f;for (int k = left; k < right; k++){int temp=solve(a, left, k) + solve(a, k + 1, right) + a[left][0] * a[k][1] * a[right][1];if (temp < min_num)min_num = temp;}return min_num;}

1022 最长公共子序列

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

给定两个字符串A和B, 请计算这两人个字符串的最长公共子序列长度。

Input

第一行输入M(M<=10)表示有M组数据。每组数据输入两行字符串, 字符串的长度不长于500。

Output

输出M行正整数,第i行表示第i组数据的最长公共子序列长度。

Sample Input

2

abcdefg

cemg

abcdefgh

ceaaegh

Sample Output

3

4

这是老师课上的一道dp例题。

最优子结构性质:

设X=(x1, …, xm)、Y=(y1, …, yn)是两个序列,Z=(z1, …, zk)是X与Y的LCS,则有:

⑴ 如果xm=yn,

则zk=xm=yn, Z(k-1)是X(m-1)和Y(n-1)的LCS,

即LCS(m,n) = LCS(m-1,n-1) + 1.

⑵ 如果xm≠yn,

若最终zk≠xm,则 LCS(m,n)= LCS(m-1,n)

若最终zk≠yn ,则 LCS(m,n)= LCS(m,n-1)

LCS(m,n)=max{LCS(m-1,n), LCS(m,n-1)}

LCS递归方程:

LCS[i,j]=0 ; if i=0 or j=0

LCS[i,j]=LCS[i-1, j-1]+1 ; if i,j>0,xi=yj

LCS[i,j]=Max(LCS[i,j-1],LCS[i-1,j]) ; if i,j>0,xi≠yj

#include<iostream>#include<string>#include<cstdio>#include<algorithm>#include<cstring>using namespace std;int LCS[1001][1001];int main(){int m, n, k;char str1[1001], str2[1001];cin >> m;for (int x = 0; x < m; x++){for (int i = 0; i < 1001; i++)for (int j = 0; j < 1001; j++)LCS[i][j] = 0;cin >> str1 >> str2;int len1 = strlen(str1);int len2 = strlen(str2);for (int i = 1; i <= len1; i++)for (int j = 1; j <=len2; j++)if (str1[i-1] == str2[j-1])LCS[i][j] = LCS[i - 1][j - 1] + 1;elseLCS[i][j] = LCS[i-1][j] > LCS[i][j-1] ? LCS[i-1][j] : LCS[i][j-1];cout << LCS[len1][len2] << endl;}return 0;}

1027 带权活动选择

Time Limit: 3000 MS Memory Limit: 1000 KB

Description

给定n个活动,活动ai表示为一个三元组(si,fi,vi),其中si表示活动开始时间,fi表示活动的结束时间,vi表示活动的权重,si<fi。带权活动选择问题是选择一些活动,使得任意被选择的两个活动ai和aj执行时间互不相交,即区间[si,fi)与[sj,fj)互不重叠,并且被选择的活动的权重和最大。请设计一种方法求解带权活动选择问题。

Input

第一行输入M(M<=10)表示有M组数据。每组数据输入整数N(N<=10000), 接下来输入N个活动。

Output

输出M行正整数,第i行表示第i组数据的能够选择活动最大权值和。

Sample Input

2

5

7 9 9

7 8 1

6 7 9

6 8 5

4 9 9

5

4 7 9

3 4 4

7 8 8

8 9 6

4 5 9

Sample Output

18

27

有个不带权的活动选择题目,但是那是贪心法才可求解,而这题有了加权只(?)能dp做。(稍后做个题目对比)

//不是贪心,而是dp#include<iostream>using namespace std;void QuickSort(int a[], int x, int y);void Swap(int& x, int& y);int* s = new int[10000], * f = new int[10000], * v = new int[10000];//其中si表示活动开始时间,fi表示活动的结束时间,vi表示活动的权重,si < fiint main(){int m, n, k;cin >> m;for (int i = 0; i < m; i++){cin >> n;for (int j = 0; j < n; j++)cin >> s[j] >> f[j] >> v[j];QuickSort(f, 0, n - 1);//sort by end timeint* max = new int[n],*p=new int[n];for (int j = 0; j < n; j++){max[j] = 0; p[j] = -1;}for (int j = 0; j < n; j++)for (int t = j - 1; t >= 0; t--)if (s[j] >= f[t]){p[j] = t;//使得活动t与j相容的最大标记,t<jbreak;}for (int j = 0; j < n; j++)if(p[j]!=-1)max[j] = (max[j - 1] > max[p[j]] + v[j] ? max[j-1] : max[p[j]] + v[j]);else max[j] = (max[j - 1] > v[j] ? max[j - 1] : v[j]);cout << max[n-1] << endl;}return 0;}void QuickSort(int a[], int x, int y){if (x >= y)return;int pivot = a[x], pivotpos = x;for (int i = x; i <= y; i++){if (a[i] < pivot){pivotpos++;if (pivotpos != i)//小于基准的交换到左侧{Swap(a[pivotpos], a[i]);Swap(s[pivotpos], s[i]);Swap(v[pivotpos], v[i]);}}}Swap(a[x], a[pivotpos]);Swap(s[x], s[pivotpos]);Swap(v[x], v[pivotpos]);QuickSort(a, x, pivotpos - 1);QuickSort(a, pivotpos + 1, y);}void Swap(int& x, int& y){int temp = x;x = y;y = temp;}

1034 树上着色

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

对一棵树进行着色,每个结点可着黑色或白色,相邻结点不能着相同黑色,但可着相同白色。请设计一种算法对树中尽量多的节点着黑色。

Input

第一行输入T(T<=10)表示有T组数据。每组数据先输入一个正整数N(1<=N<=50000),表示共有N个结点,接下来输入N-1对(u,v),表示u与v之间有一条边。

Output

输出T行正整数,第i行表示第i棵树最多能着色几个黑点。

Sample Input

3

2

1 2

4

1 2

2 3

3 4

4

1 2

1 3

1 4

Sample Output

1

2

3

Sample Input

321 241 22 33 441 21 31 4

Sample Output

123

首先解析题目:父节点涂黑色时,它的子节点只能涂白色,而父节点涂白色时,他的子节点可以涂白色或黑色。其次,这题也是我为数不多的采用了vector来做,本想开一个动态二维数组,但是分配空间有点麻烦;result[][2]区分涂白色或涂黑色这两种情况(result[][0] = 0; //涂白色,result[][1] = 1;//涂黑色),为何不用一维数组呢?是因为初始化时有两种选择,当然你完全可以开两个一维数组,那不就等价于result[][2]嘛!接下来就是递归求解啦,找出根节点按照涂黑涂白进行赋值,把所有子节点继续当成根节点进行遍历,将子问题得到的着黑色点最大值按照题目要求都相加,最后取两种情况(根节点涂黑、涂白)的较大值即为所求。

核心如下:(注意根节点涂黑时,只能相加子节点涂白的结果值)

result[根节点][0] += max(result[子节点][0], result[子节点][1]);//根节点涂白

result[根节点][1] += result[子节点][0];//根节点涂黑

#include<iostream>#include<algorithm>#include<vector>using namespace std;int result[50001][2];//表示每个结点可以涂(1)为白色或者不涂(0)为黑色void dp(int root, vector<int> son[50001]);int main(){int T, N, u, v;int P[50001];cin >> T;for (int i = 0; i < T; i++){vector<int> son[50001];cin >> N;// (1 <= N <= 50000),表示共有N个结点for (int j = 0; j <= 50000; j++)for (int k = 0; k < 2; k++)result[j][k] = 0;for (int j = 0; j <= 50000; j++)P[j] = 0;//初始化都没有父结点for (int j = 0; j < N - 1; j++){cin >> u >> v;son[u].push_back(v);//u有一个子节点vP[v] = 1;//v有父结点u}int root;for (int j = 1; j <= N; j++) {//找到根节点(某个没有父结点的结点)if (!P[j]) {root = j;break;}}dp(root, son);cout << max(result[root][0], result[root][1]) << endl;}return 0;}void dp(int root, vector<int> son[50001]) {result[root][0] = 0; //不涂黑色result[root][1] = 1;//涂黑色for (int i = 0; i < son[root].size(); i++) {int temp = son[root][i];dp(temp, son);//把子节点继续当成根节点进行遍历//涂(1)或者不涂(0)为黑色两种情况都需要记录result[root][0] += max(result[temp][0], result[temp][1]);result[root][1] += result[temp][0];}}

1042 最低票价

Time Limit: 1000 MS Memory Limit: 262144 KB

Description

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有三种不同的销售方式:

一张为期一天的通行证售价为 costs[0] 美元;

一张为期七天的通行证售价为 costs[1] 美元;

一张为期三十天的通行证售价为 costs[2] 美元。

通行证允许数天无限制的旅行。

例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。

Input

第一行输入nums表示有nums组测试

对每组测试用例

第一行输入m

第二行输入具有m个元素的days数组,days[i]表示你将在days[i]这天旅行

第三行输入具有3个元素的costs数组,具体释义见Description

Output

对每组测试数据,输出你想要完成在给定的 days数组 中列出的每一天的旅行所需要的最低消费。

提示:

1 <= days.length <= 365

1 <= days[i] <= 365

days 按顺序严格递增

costs.length == 3

1 <= costs[i] <= 1000

Sample Input

2

6

1 4 6 7 8 20

2 7 15

12

1 2 3 4 5 6 7 8 9 10 30 31

2 7 15

Sample Output

11

17

做到 【1042】了,看这题也是能很容易看出是可以动规一波滴,f[i]表示前i天去旅行的天数所需要的最低消费,对于不在days数组中的天数i当天花费必然为0,所以用前一天的f[i-1]初始化,对于在days数组中的天数i,如下初始化:f[j] = min(min(f[j - 1] + costs[0], f[j - 7] + costs[1]), f[j- 30] + costs[2])。而为了下标j-30非负,将数组前30个元素设为0,更新从f[30]开始,这种处理很常见滴。

#include<iostream>#include <iomanip>#include<algorithm>using namespace std;int nums, m;int* days;double costs[3];double f[366+30];//int costs[3];//int f[366];int Indays(int x);int main(){cin >> nums;for (int i = 0; i < nums; i++){cin >> m;days= new int[m];for (int j = 0; j < 366+30; j++)f[j] = 0;for (int j = 0; j < m; j++)cin >> days[j];for (int j = 0; j < 3; j++)cin >> costs[j];for (int j = 1; j <= days[m-1]; j++)if (!Indays(j))f[j+30] = f[j+30 - 1];else {//if (j >= 30)f[j+30] = min(min(f[j+30 - 1] + costs[0], f[j+30 - 7] + costs[1]), f[j+30 - 30] + costs[2]);/*else {if (j >= 7)f[j] = min(f[j - 1] + costs[0], f[j - 7] + costs[1]);else f[j] = f[j - 1] + costs[0];}*/}cout << f[days[m - 1]+30] << endl;}return 0;}int Indays(int x){for (int i = 0; i < m; i++)if (days[i] == x)return 1;return 0;}/*[1,4,6,7,8,20][2,7,15][7,2,15]*/

1043 鸡蛋掉落

Time Limit: 1000 MS Memory Limit: 262144 KB

Description

你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。

每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。

你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。

每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。

你的目标是确切地知道 F 的值是多少。

无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

Input

第一行输入nums表示有nums组测试

每组测试输入K, N,表示有K个鸡蛋,N层楼

Output

对每组测试数据,输出确定F的最小移动次数

Sample Input

3

1 2

2 6

3 14

Sample Output

2

3

4

提示: 1 <= K <= 100 1 <= N <= 10000

Sample Input

31 22 63 14

Sample Output

234

原先想的是这题也是一道动规思想(dp[i][j]表示在j层楼的情况下用i个鸡蛋检测所需移动的最小次数)可以解决的题目,

楼层数为0或者鸡蛋数为0,那最少的移动次数就是0,即f(0,N)=0,f(K,0)=0;鸡蛋数为1,那就需要从第一层开始逐步向上扔,所以最少的移动次数为楼层数,即f(1,N)=N;当楼层数大于0且鸡蛋数大于1,假设有K个鸡蛋,N层楼,首先从X层将鸡蛋扔下,这是会有两种情况:

(1)鸡蛋碎了:那就排除了x层之上包括x的楼层,同时损失了一枚鸡蛋,所以有1+f(K-1,X-1);

(2)鸡蛋没碎:那就排除了x层之下包括x的楼层,并且没有损失鸡蛋,所以有1+f(K,N-X)。

需要在这两个值中选取较大的值(最坏的情况∵题目中说无论F 的初始值如何,确定最小移动次数)。

我也就写出了下面代码中注释的解法,确实可以得到正解,但是它它它又又又tle了。。。。(确实,时间复杂度过高O(M*M*N),O(M*N)。一旦数据过大,计算就会非常大)

最终这道题AC也是借鉴的其他博主的另一种解法(完全的输了,dp代表的含义都不一样,菜死我算了),核心方法如下阐述:

无论你在哪层楼扔鸡蛋,鸡蛋只可能摔碎或者没摔碎,碎了的话就测楼下,没碎的话就测楼上。无论你上楼还是下楼,总的楼层数 = 楼上的楼层数 + 楼下的楼层数 + 1(当前这层楼)。根据这个特点,可以写出下面的状态转移方程:dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1,其中dp[k][m - 1]就是楼上的楼层数,因为鸡蛋个数k 不变,也就是鸡蛋没碎,扔鸡蛋次数 m 减一;dp[k - 1][m - 1]就是楼下的楼层数,因为鸡蛋个数 k减一,也就是鸡蛋碎了,同时扔鸡蛋次数 m 减一。上述递推公式可以这样理解,一次扔鸡蛋至少能推测1层楼,剩余m-1次扔鸡蛋则分别可以推测dp[k-1][m-1]和dp[k][m-1]层楼;dp[k-1][m-1]表示如果这次扔鸡蛋破了,那么只剩下k-1个鸡蛋和m-1次扔鸡蛋的机会可以探测到的最高楼层数;dp[k][m-1]表示这次扔鸡蛋没有破,还剩下k个鸡蛋和m-1次扔鸡蛋机会可以探测到的最高楼层数;同时还有本身扔鸡蛋的这一层楼“1”。最后当dp的某个值达到总楼层数N,此时对应的m即为所求最小移动次数。

#include<iostream>using namespace std;int nums, N,K;int dp[101][10001];int main(){cin >> nums;for (int i = 0; i < nums; i++){cin >> K >> N;int m = 0;while(dp[K][m] < N) {m++;for (int k = 1; k <= K; k++)dp[k][m] = 1 + dp[k - 1][m - 1] + dp[k][m - 1];}cout<<m<<endl;}return 0;}/*for (int j = 1; j <= N; j++)dp[1][j] = j;for (int j = 1; j <= K; j++)dp[j][1] = 1;for (int j = 2; j <=K; j++)for (int w = 2; w <= N; w++){int minVal = 0x3f3f3f3f;//d[j][w] = 0x3f3f3f3f;for (int t = 1; t <= w; t++)minVal = min(minVal, 1 + max(dp[j - 1][t - 1], dp[j][w - t]));//dp[j][w] = min(dp[j][w],1 + max(dp[j - 1][t - 1], dp[j][w - t]));dp[j][w] = minVal;}cout << dp[K][N] << endl;*/

【神奇的思路】(小姑凉想不到,麻麻咪呀)

1013 逆序列

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

给定一个长度为N的int型数组a[0,1,2,…N-1], 请计算逆序对个数.当i<j且a[i]>a[j], 则称a[i]与a[j]是一对逆序对.

Input

第一行输入M表示包含M组测试数据,每组先输入N (N<=50000), 接着输入N个int型整数.

Output

输出逆序对个数.

Sample Input

2

5 1 5 2 1 3

6 85 16 44 99 66 1

Sample Output

4

9

同理下面这道题也是逆序对数问题

1017 电路布线

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

一长方形电路板两长边分别有n个焊点, 分别记作1,2,…,n. 现需要将一边的焊点与另一边的焊点用导线相连,

共需要n条导线连接n对焊点. 我们用(i,xi)来表示一根导线的连接方式, 即一边的第i点与另一边的第xi相连.

两条导线(i,xi)和(j,xj)交叉, 当i<j且xi>xj, 或者i>j且xi<xj.

当给定焊点的连接方式时, 请设计一分治算法计算有交叉点的个数.

Input

第一行输入m表示有m组测试. 每组测试首先输入n (n<50000),接下来输入n个int型整数, 表示xi.

Output

对每组测试数据输出交叉点的个数

Sample Input

2

8

2 5 1 7 6 3 8 4

8

3 1 6 8 2 5 4 7

Sample Output

10

10

关于逆序对的个数求解,最简单且易懂的方法一便是循环比较遇到前者大于后者的数对,便num++,但是tle鸭,不用想怎么可能这么简单,然后我就卡住了。

得亏CSDN大神众多,也是让小禾第一次见识到了什么是神奇的算法思路。

方法二:众所周知,归并排序就是将一个序列分成两部分进行排序,然后将排序后的两个序列中的元素一个一个插入;

然后,我们发现,对于两个正在排列的子序列,若后面的子序列中要插入元素,则前面的子序列中剩下的待排序元素都可以与当前插入的元素组成逆序对(因为前面待排序的元素都比这个元素大,而且下标都比这个元素的下标要小),而且再次递归下去的子序列也是如此。

所以,我就把归并排序的算法改动一丢丢就拿来用了,如下所示:

#include<iostream>void MergeSort(int*, int, int);void merge(int*, int, int);using namespace std;int num=0;int main(){int m, n;cin >> m;for (int i = 0; i < m; i++){cin >> n; num = 0;int* a = new int[n];for (int j = 0; j < n; j++)cin >> a[j];//for (int j = 0; j < n; j++)//for (int k = j; k<n; k++)//if (a[k] < a[j])num++;MergeSort(a, 0, n - 1);cout <<num<< endl;}return 0;system("pause");}void MergeSort(int* a, int x, int y){if (x >= y)return;MergeSort(a, x, (x + y) / 2);MergeSort(a, (x + y) / 2 + 1, y);merge(a, x, y);}void merge(int* a, int x, int y){int r[50000];int p1 = x, p2 = (x + y) / 2 + 1, k = x;//错误的地方:k=x,不是0while (p1 <= (x + y) / 2 && p2 <= y) {if (a[p1] <= a[p2]) {r[k] = a[p1]; p1++;}else{num += (x + y) / 2 - p1 + 1;r[k] = a[p2]; p2++;}k++;}if (p1 > (x + y) / 2)while (p2 <= y) {r[k] = a[p2]; p2++; k++; }if (p2 > y)while (p1 <= (x + y) / 2) {r[k] = a[p1]; p1++; k++; }for (int i = x; i <= y; i++)a[i] = r[i];}

1014 最大子数组和

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

给定一个长度为N的int型数组a[0,1,2,…N-1], 请计算最大子数组和.

Input

第一行输入M表示包含M组测试数据,每组先输入N (N<=50000), 接着输入N个int型整数.

Output

输出最大子数组和.

Sample Input

2

5 -1 -5 -2 -1 -3

5 2 -1 3 -2 4

Sample Output

-1

6

首先,审题这里的子数组是数组中连续的一段,从而求最大子数组和的最简单方法一便是两层循环,每次计算以a[j]为起始的子数组中最大的和,取最大即为所求,nice,又tle啦。

而稍微难想到(可见我还是缺乏思考www)的方法二是用数组sum[i]记录a[0]到a[i]的这一段中最大子数组和,sum[i]的计算基于sum[i-1]的值,核心思想如下:

if (sum[i - 1] > 0) sum[i] = sum[i - 1] + a[i];

else sum[i] = a[i];

#include<iostream>using namespace std;int main(){int m, n;cin >> m;for (int i = 0; i < m; i++){cin >> n;int* a = new int[n];int* sum = new int[n];for (int j = 0; j < n; j++)cin >> a[j];/*int max_sum=a[0],t=0;for (int j = 0; j < n; j++){int temp = 0;for(int k=j;k<n;k++){temp += a[k];if (temp >= max_sum) max_sum = temp;}}cout << max_sum << endl;*/sum[0] = a[0]; int max_sum = a[0];for (int i = 1; i < n; ++i){if (sum[i - 1] > 0)sum[i] = sum[i - 1] + a[i];elsesum[i] = a[i];if (sum[i] > max_sum)max_sum = sum[i];}cout << max_sum << endl;}return 0;}

1023 穷游?“穷”游 ?

Description

贫穷的小A有一个梦想,就是到t国去一次穷游,但现实是残酷的。小A所在的世界一共有n(n<=500)个国家,国家与国家之间总共有E(E<=50000)条道路相连,第i个国家对于进入它的外国人都要收取Bi的费用,而小A家住在s国,他必须通过这些道路在各个国家之间中转最终到达t国(除非他运气够好可以直接从s国到达t国)。但是贫穷的小A只剩下M(M<=100)元家底了,因此他必须精打细算旅途的费用,同时小A对于t国实在太向往了,因此他希望能够走最短的路尽快到达t国。这个问题难倒了小A,现在他请你帮他算一算他到达t国的最短路径有多长。

Input

第一行输入T(T<=10)表示有T组数据。每组数据第一行输入n、E、s、t、M,分别表示小A所在世界的国家数、国家之间的总道路数、小A的国籍、小A向往的国家以及小A的家底;接下来一行输入n个正整数Bi,表示第i个国家收取的过路费(由于小A是s国人,因此s国不会收取,但t国会);接下来输入E行每行三个正整数u(1<=u<=n)、v(1<=v<=n)、w,表示u国和v国之间存在着一条长度为w的无向边(可能有重边)。输入保证最终结果不会使int溢出。

Output

输出T行正整数,第i行表示第i组数据小A花费不超过M元到达t国的最短路。若小A无法到达t国,输出-1.

Sample Input

3

2 2 1 2 10

20 10

1 2 1

1 2 2

3 1 1 3 10

1 1 1

2 3 1

3 3 1 3 10

1 11 1

1 2 1

1 2 3

2 3 1

Sample Output

1

-1

-1

关于处理最短路,掌握的是dijktra算法,而对于这题有花费限制的最短路问题,有双重限制条件,在更新dist数组时就出现了问题,看了网上很多都是spfa最优队列解法,很巧妙,但是自己理解还是不透彻,最后左看右瞧用bellman-ford算法加上dp(我感觉像是动规思想)总算倒数第二名完成了这道【1023】。

bellman-ford的核心就是只要队列外已修改dist数组的点要重新加入队列中,不断更新直到队列为空。为什么我说感觉自己像是用了一个动规思想呢,因为dist我设置的二维数组,dist[i][j]代表从点s到点i花费的钱数为j的最短距离,之后更新dist前多了一个for循环,不断增加花费的钱数限制,以更新到局部最短路。

//费用限制的最短路径(标解都是用spfa,不太能够自己理解)瞎写的dp加上bellman-ford#include<iostream>#include<queue>#include<string.h>using namespace std;#define INF 0x3f3f3f3f//void shortestPath(int s, int *dist);//Dijkstra//void costDijkstra(int t, int* c);//void dfs(int t,int c);//void spfa();queue <int> q;int B[501], dist[501][101];//int dist[501],c[501];//dist最短的路径,c(到终点)最小的花费int w[501][501];bool visited[501];int T, n, E, s, t, M, u, v, cost = 0;int main(){cin >>T;for (int i = 0; i < T; i++){cin >> n >> E >> s >> t >> M;//表示小A所在世界的国家数、国家之间的总道路数、小A的国籍、小A向往的国家以及小A的家底cost = 0;memset(B, 0, sizeof(B));memset(visited, false, sizeof(visited));for (int j = 1; j <= n; j++)cin >> B[j];//表示第i个国家收取的过路费B[s] = 0;//由于小A是s国人,因此s国不会收取for (int j = 1; j <= n; j++)for (int k = 1; k <= n; k++)w[j][k] = INF;for (int j = 1; j <= E; j++){int x;cin >> u >> v >> x;if (x < w[u][v])w[u][v] = w[v][u] = x;}for (int j = 1; j <= n; j++)for (int k = 0; k <= M; k++)if (j == s)dist[j][k] = 0;else dist[j][k] = INF;//dist[j][k]表示从点s到点j花费的钱数为k的最短距离int next = s, res = INF;while (!q.empty())q.pop();visited[s] = 1;q.push(s);while (!q.empty()){next = q.front();q.pop();visited[next] = 0;for (int x = 1; x <= n; x++)if (w[next][x] != INF)for (int k = B[x]; k <= M; k++)if (dist[next][k - B[x]] + w[next][x] < dist[x][k]){dist[x][k] = dist[next][k - B[x]] + w[next][x];if (!visited[x]){visited[x] = 1;//以x为起点的路的长度可能也改变 q.push(x);}}}for (int k = 0; k <= M; k++)if (dist[t][k] < res)res = dist[t][k];if (res != INF)//有最短路径(花费不超过M元)cout << res << endl;else cout << -1 << endl;}return 0;}

1024 最优二叉搜索树

Time Limit: 4000 MS Memory Limit: 5000 KB

Description

给定N个整数关键字, 每个关键字有一搜索概率, 关键字外区间(共N+1个区间)也有搜索概率.可根据关键字构造二叉搜索树来减少搜索代价. 假定二叉搜索树中关键字节点的搜索代价为该点到树的根节的路径上关键字节点的个数, 关键字之外区间的搜索代价也为该区间到树的根的路径上关键字节点的个数. 二叉搜索树的期望代价为所有关键字和关键字之外区间的期望代价之和. 请构造一棵最优二叉搜索树, 计算最优二叉搜索树的期望代价

Input

第一行输入M(M<=10)表示有M组数据。每组数据先输入N(N<=500), 表示N个关键字. 接下来输入一行N个从小到大排好序的关键字, 一行N个关键字的搜索概率, 以及一行N+1个关键字外区间的搜索概率。

Output

输出M行正整数,第i行表示第i组数据的最优二叉搜索树的期望代价, 保留小数点后6位。

Sample Input

2

2

10 20

0.1 0.3

0.2 0.2 0.2

3

10 20 30

0.1 0.2 0.3

0.1 0.1 0.1 0.1

Sample Output

1.500000

1.800000

又是一道dp例题练习。

话不多说直接放ppt(懒洋洋)

#include<iostream>#include<iomanip>using namespace std;#define INF 0x3f3f3f3fvoid BST(double[], double[], int);int key[500];double p[501], q[501],c[501][501],w[501][501];int main(){int M, N;cin >> M;//M<=10for (int i = 0; i < M; i++){cin >> N;//表示N个关键字for (int j = 0; j < N; j++)cin >> key[j];//N个从小到大排好序的关键字for (int j = 1; j <= N; j++)cin >> p[j];//N个从小到大排好序的关键字for (int j = 0; j <= N; j++)cin >> q[j];//一行N+1个关键字外区间的搜索概率BST(p,q, N);cout << fixed<<setprecision(6)<<c[0][N] << endl;}return 0;}void BST(double p[], double q[], int n){for (int i = 0; i <= n; i++){c[i][i] = 0;w[i][i] = q[i];}for (int x = 1; x <= n; x++)for (int i = 0; i <= n - x; i++){int j = i + x;c[i][j] = INF;w[i][j] = w[i][j - 1] + p[j] + q[j];for (int k = i+1; k <= j; k++){double t = w[i][j] + c[k][j]+ c[i][k - 1];if (t < c[i][j])c[i][j] = t;}}}

【背包问题】(多彩的背包啊。)

1019 0/1背包问题1 (最简单的0-1背包)

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

有一个容量为C(C<=100)的背包以及N(N<=500)颗宝石,第i颗宝石大小为si,价值为vi。由于条件限制,

你手边只有这个背包可作为你搬运宝石的唯一工具。现在你想知道在最多可以带走多大价值的宝石。

Input

第一行输入M(M<=10)表示有M组数据。每组数据第一行输入N、C,表示宝石数目以及背包容

量;接下来一行输入N组(si,vi), si和vi均为整数,表示每颗宝石的大小和价值。

Output

输出M行正整数,第i行表示第i组数据可以带走的宝石的最大代价, 背包可不用装满。

Sample Input

3

3 10

1 3 2 5 7 2

3 10

1 3 2 5 6 2

5 10

5 6 5 7 2 8 8 1 5 9

Sample Output

10

10

17

用solve[i][j]表示前i颗宝石,容量为j的背包最多可以带走的宝石价值。核心思想如下(以是否带走第i颗宝石分情况比较):

solve[k][j] = solve[k - 1][j];//初始填充if (j - s[k] >= 0)solve[k][j] = (solve[k - 1][j] > (solve[k - 1][j - s[k]] + v[k]) ?solve[k - 1][j] : solve[k - 1][j - s[k]] + v[k]);

#include<iostream>using namespace std;int s[501], v[501];//size:大小;value:价值int solve[501][501];int main(){int m, n, c;cin >> m;for (int i = 0; i < m; i++){cin >> n; cin >> c;//表示宝石数目以及背包容量int max = 0;for (int j = 1; j <= n; j++)cin >> s[j] >> v[j];for (int k = 1; k <= n; k++)for (int j = 0; j <= c; j++){solve[k][j] = solve[k - 1][j];//初始填充if (j - s[k] >= 0)solve[k][j] = (solve[k - 1][j] > (solve[k - 1][j - s[k]] + v[k]) ?solve[k - 1][j] : solve[k - 1][j - s[k]] + v[k]);if (solve[k][j] > max)max = solve[k][j];}cout << max << endl;}return 0;}

1018 0/1背包问题2(恰好被装满才可带走的背包)

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

有一个容量为C(C<=100)的奇怪背包,这个背包可以被带走仅当它恰好被装满。现在你手边有N(N<=500)颗宝石,第i颗宝石大小为si,价值为vi。由于条件限制,你手边只有这个奇怪的背包可作为你搬运宝石的唯一工具。现在你想知道在这样的条件下你最多可以带走多大利润的宝石。

Input

第一行输入M(M<=10)表示有M组数据。每组数据第一行输入N、C,表示宝石数目以及背包容量;接下来一行输入N组(si,vi), si和vi均为整数,表示每颗宝石的大小和价值。

Output

输出M行正整数,第i行表示第i组数据可以带走的宝石的最大代价, 背包可被带走仅当它恰好被装满。

Sample Input

3

3 10

1 3 2 5 7 2

3 10

1 3 2 5 6 2

5 10

5 6 5 7 2 8 8 1 5 9

Sample Output

10

0

16

加了一个限制条件:背包可被带走仅当它恰好被装满

再思考时,发现刚刚的 0/1背包问题1,其实不用开二维数组滴,从上一种方法可以看出,在计算solve[i][j]时只使用了solve[i-1][0……j],所以使用一维滚动数组依次覆盖即可。

现在这题就用一维解决好啦。

不过用一维滚动数组有一点需要明确的,这也是我一开始一直WA的原因:内层循环是逆序,原因是我们在求solve[j]的时候需要用到solve[j-1],如果采用正序,当到solve[j]时solve[j-1]已经是这一行的状态了,没办法与前一行再进行比较。一定要记住!

核心思想如下:

#include<iostream>using namespace std;int s[501],v[501];//size:大小;value:价值int solve[501];int main(){int m, n, c;cin >> m;for (int i = 0; i < m; i++){solve[0] = 0;for (int i = 1; i <= 500; i++)solve[i] = -0x3f3f3f3f;cin >> n; cin >> c;//表示宝石数目以及背包容量int max = 0;for (int j = 1; j <= n; j++)cin >> s[j]>>v[j];for (int k = 1; k <= n; k++)for (int j = c; j >= s[k]; j--)///关键{solve[j] = (solve[j] > (solve[j - s[k]] + v[k]) ?solve[j] : solve[j - s[k]] + v[k]);if (solve[j] > max)max = solve[j];}if(solve[c] >0)cout << solve[c] << endl;elsecout << 0 << endl;}return 0;}

下面这个应用题就是披着“实际生活”皮的“背包问题”,具体原理不要过多阐述,如果你懂简单的背包问题解法,直接见实现就会发现,其实完全可以套用1019 0/1 背包问题1。

1021 钢条切割

Time Limit: 1000 MS Memory Limit: 1000 KB

Description

给定一根长度为n(n<=10000)的钢条以及一张价格表, 请计算这根钢条能卖出的最大总收益. 价格表表示为(li,pi), 1<=i<=k. 不在价格表中的钢条可卖出价格为0。

Input

第一行输入m(m<=10)表示有M组数据。每组数据第一行输入两个int型整数n和k,分别表示钢条长度以及价格表中不同价格数量. 接下来一行输入k个价格的表示(li,pi), 均为整数, li可能大于n。

Output

输出m行整数,第i行表示第i组数据的最大总收益。

Sample Input

2

27 3

35 41 61 49 73 74 ‘

94 2

21 55 88 64

Sample Output

0

220

#include<iostream>using namespace std;int l[10001];int p[10001];int solve[10001];int main(){int m, n, k;cin >> m;for (int x = 0; x < m; x++){cin >> n >> k;//分别表示钢条长度以及价格表中不同价格数量int max = 0;for (int i = 0; i <= n; i++)solve[i] = 0;for (int i = 1; i <= k; i++)cin >> l[i] >> p[i];for (int i = 1; i <= k; i++)for (int j = l[i]; j <= n; j++){solve[j] = (solve[j] > (solve[j - l[i]] + p[i]) ? solve[j] : (solve[j - l[i]] + p[i]));if (solve[j] > max)max = solve[j];}cout << max << endl;}return 0;}

【这些经典】

1028 dijkstra算法

Time Limit: 2000 MS Memory Limit: 5000 KB

Description

给定n(n<=500)个顶点,以及E(E<=10000)条边,使用迪杰斯特拉算法计算顶点s到顶点t的最短路径.

Input

第一行输入T表示有T组数据。每组数据第一行输入n、E、s、t,分别表示顶点数、边数、顶点s以及顶点t. 接下来输入E行每行三个正整数u(1<=u<=n)、v(1<=v<=n)、w,表示顶点u到顶点v之间无向边长度w(可能有重边)。

Output

输出T行正整数,第i行表示第i组数据s到达t的最短路径长度。若s无法到达t国,输出-1.

Sample Input

3

2 2 1 2

1 2 1

1 2 2

3 1 1 3

2 3 1

3 3 1 3

1 2 1

1 2 3

2 3

1

Sample Output

1

-1

2

#include<iostream>using namespace std;#define INF 0x3f3f3f3fvoid shortestPath(int n, int s, int* dist);int w[501][501];bool ss[501];int main(){int T, n, E, s, t, u = 0, v = 0;cin >> T;for (int i = 0; i < T; i++){cin >> n >> E >> s >> t ;//表示顶点数、边数、顶点s以及顶点tint* dist = new int[n];for (int j = 0; j < 501; j++)for (int k = 0; k < 501; k++)w[j][k] = INF;for (int j = 1; j <= E; j++){int x;cin >> u >> v >> x;if (x < w[u][v]) {w[u][v] = x;w[v][u] = x;}//有重边时选路短的保留}shortestPath(n, s, dist);if (dist[t] != INF)//有最短路径cout << dist[t] << endl;else cout << -1 << endl;}return 0;}void shortestPath(int n, int s, int* dist)//共n个顶点,求从s到t的最短路径{for (int i = 1; i <= n; i++){dist[i] = w[s][i];ss[i] = false;}ss[s] = true;dist[s] = 0;for (int i = 1; i < n; i++)//除去s还有n-1个国家{int u = s;int min = INF;for (int j = 1; j <= n; j++)//选最小路径if (ss[j] == false && dist[j] < min){u = j;min = dist[j];}ss[u] = true;for (int k = 1; k <= n; k++)if (ss[k] == false && w[u][k] + dist[u] < dist[k])dist[k] = w[u][k] + dist[u];}}

1029 最小生成树

Time Limit: 2000 MS Memory Limit: 5000 KB

Description

给定n(n<=500)个顶点,以及E(E<=20000)条边,计算最小生成树的权值.

Input

第一行输入T表示有T组数据。每组数据第一行输入n、E,分别表示顶点数和边数. 接下来

输入E行每行三个正整数u(1<=u<=n)、v(1<=v<=n)、w,表示顶点u到顶点v之间无向边

长度w(可能有重边)。

Output

输出T行正整数,第i行表示第i组数据的最小生成树权值, 若不能构建最小生成树输出-1。

Sample Input

32 21 2 11 2 23 12 3 13 31 2 21 2 32 3 1

Sample Output

1-13

用的prim算法

#include<iostream>using namespace std;int w[501][501];bool s[501];int lowcost[501];int main(){int m, n, E;//分别表示顶点数和边数cin >> m;for (int i = 0; i < m; i++){int x,len=0;for (x = 0; x < 501; x++)for (int y = 0; y < 501; y++)w[x][y] = 0x3f3f3f3f;cin >> n >> E;for (int j = 1; j <= E; j++)//Prim算法{int u,v,x;cin >> u >> v >> x;if (x < w[u-1][v-1]) {w[u-1][v-1] = x;w[v-1][u-1] = x;}//有重边时选路短的保留}for (x = 0; x < n; x++){s[x] = false;lowcost[x] = w[0][x];}s[0] = true;for (x = 0; x < n - 1; x++){int min = 0x3f3f3f3f,key=0;for (int w = 0; w < n; w++){if (s[w] == false && lowcost[w] < min){min = lowcost[w];//更新min的值key = w;//记录当前最小权重的节点的编号}}if (min == 0x3f3f3f3f)break;//cout << key << endl;s[key] = true;//表明key节点已被选了(作标记)len += min;for (int k = 0; k < n; k++){if (key != k && w[key][k] < lowcost[k])lowcost[k] =w[key][k];}}if (x== n-1)cout << len << endl;else cout << -1 << endl;}return 0;}

1036 KMP水题

Time Limit: 1000 MS Memory Limit: 10000 KB

Description

给定文本串s与模式串t,求s中有多少个子串与t相同,两个子串视为不同仅当他们长度不等或起始位置不同。

Input

第一行输入T(T<=100)表示有T组数据。每组数据先输入两个正整数n、m(1<=n<=100000,1<=m<=n),分别表示文本串与模式串长度。紧接着输入两行字符串,即为s、t。

Output

输出T行正整数,第i行表示第i组文本串中有多少个子串与模式串相同。

Sample Input

2

5 3

ababa

aba

3 1

aaa

a

Sample Output

2

3

KMP再谈有点老生常谈的感觉了,直接见代码,好嘛?——好!

#include<iostream>#include<iomanip>#include<stdio.h>#include<cstring>#include<string>using namespace std;int KMPFind(char* T, char* P, int k, int next[]);void getNext(char* a, int *next);int M, n, m;char* T = new char[100001], * P = new char[100001];//目标串target,子串patternint* nt = new int[100001];int main(){cin >> M;for (int i = 0; i < M; i++){cin >> n>> m;//n、m(1<=n<=100000,1<=m<=n)int k = 0, num = 0;for (int j = 0; j < n; j++)cin >> T[j];for (int j = 0; j < m; j++)cin >> P[j];getNext(P, nt);while(k <= n-m ){int x = KMPFind(T, P, k, nt); if (x != -1){num++; k = x + 1;}else break;}cout <<num<< endl;}return 0;}void getNext(char *a, int *next){int j = 0, k = -1;next[0] = -1;while (j < m)if (k == -1) {j++; k++; next[j] = k;}else {if (a[j] == a[k]) {j++; k++; next[j] = k;}else k = next[k];}}int KMPFind(char *T, char *P, int k, int *next){int posP = 0, posT = k;while (posP < m && posT < n)if (posP == -1 || P[posP] == T[posT]){posT++; posP++;}elseposP = next[posP];if (posP < m) return -1;else return posT - m;}

各色排序

1003 冒泡

核心:

两个循环:

通过if(a[j]>a[j+1])swap(a[j],a[j+1]);每次将最大的数选出排在最后。

1004 归并

核心:

分治思想:

以mid=(x+y)/2划分成两部分,分别归并排序,核心是merge函数。

merge思想:

采用两个指针p1,p2,分别指向数组两部分开始位置,

比较a[p1],a[p2]取小者放入一个新的数组r[],直至有一指针指向末尾位置,

若另一指针未指向末尾,此时无需比较,将剩余部分直接放入r数组。

最终,r数组即为排好序的数组。

1005 快排

核心:

Partition:

任取一元素x为基准pivot(如选第一个),用pivotpos定位基准位置;

小于x的元素放在x左边,大于等于x的元素放在x右边;

分治思想:

对左右部分递归执行上一步骤(Partition)直至只有一个元素。

【核心代码】

void QuickSort(int* a, int x, int y){if (x >= y)return;int pivot = a[x],pivotpos=x;for(int i=x;i<=y;i++){if (a[i] < pivot){pivotpos++;if (pivotpos != i)//小于基准的交换到左侧Swap(a[pivotpos], a[i]);}}Swap(a[x], a[pivotpos]);QuickSort(a, x, pivotpos-1);QuickSort(a,pivotpos+1, y);}

1006 堆排

核心:(若是从小到大排序)

(一)可用于选出第n大

用一维数组建立最大堆,以0为首标序,父节点序号是左子节点序号的2倍加一;

用SiftDown函数向下调整;

循环执行以下步骤,直至所有元素出堆

每次堆顶元素(即最大元素)存入一个新开的数组,

再剔除最大元素后调整为最大堆。

(二)可用于选出第n小

直接用一维数组建立最小堆,建立原理同(一),即可得从小到大排好序的数组。

【贪心算法】

1030 黑白连线

Time Limit: 1000 MS Memory Limit: 1000 KB

Description

给定直线上2n个点的序列P[1,2,… ,2n],每个点P[i]要么是白点要么是黑点,其中共有n个白点和n个黑点,相邻两个点之间距离均为1,请设计一个算法将每个白点与一黑点相连,使得连线的总长度最小。例如,图中有4个白点和4个黑点,以图中方式相连,连线总长度为1+1+1+5=8。

Input

第一行输入m表示有m组测试. 每组测试首先输入n(n<=10000),接下来输入2n个0或者1, 分别表示白色或者黑色, 其中0和1的个数分别为n个.

Output

对每组测试数据输出最小总连线长度.

Sample Input

2

4

1 1 0 1 0 0 0 1

4

0 0 1 0 1 1 1 0

Sample Output

8

8

这道题是我少有的用vector解决的算法题,我本人对vector并不是很熟悉,这题选择vector的原因是它既可以像数组一样拥有索引,也可以完成像队列一样的出栈入栈工作,索引是为了计算长度,出栈入栈工作是省的我再开一个数组记录是否相连的状态。

本题解决核心:贪心,遇到黑点找最近的白点相连,遇到白点找最近的黑点相连,因此维护了两个vector(blackwhite),分别记录那些遇到的点却暂时未能相连的点,有了这样两个向量容器,每次寻找最近的点,便可以去另一个容器中查看是否为空,若非空,容器最末一个元素即为最近可相连的那个点。

当然,你同样可以用数组来做,我发现也是可以AC的,注释掉部分可供参考。

#include<iostream>#include<vector>using namespace std;int main(){int m, n;//int p[20001];cin >> m;for (int i = 0; i < m; i++){int x;cin >> n;vector<int> points;for (int i = 0; i < 2 * n; i++) {cin >> x;points.push_back(x);}int len = 0;vector<int> black, white;for (int i = 0; i < points.size(); i++) {if (points[i] == 1) {//为黑点if (!white.empty()) {//找到最近的一个白点len += (i - white.back());//vector::back()用于获取向量容器的最后一个元素。white.pop_back();}else {black.push_back(i);}}else {//为白点if (!black.empty()) {//找到最近的一个黑点len += (i - black.back());black.pop_back();}else {white.push_back(i);}}}cout << len << endl;/*bool* visited = new bool[2 * n];for (x = 0; x < 2 * n; x++)//2n个点 黑或白 都未访问{cin >> p[x];visited[x] = false;}int len = 0;int key = 0;for (x = 0; x < 2 * n; x++){if (visited[x])continue;for (y = x + 1; y < 2 * n; y++)if (p[x] != p[y]&&!visited[y]){len += y - x; visited[x] = visited[y] = true;key++; break;}if (key == n) {break; }}cout << len << endl;*/}return 0;}

1031 基地布置

Time Limit: 1000 MS Memory Limit: 1000 K

Description

海面上有一些船需要与陆地进行通信,需要在海岸线上布置一些基站。现将问题抽象为,在x轴上方,给出n条船的坐标p1,p2,…,pn,其中pi=(xi,yi),0≤yi≤d, 1≤i≤n,在x轴安放的基站可以覆盖半径为d的区域内的所有船只,问在x轴至少要安放几个基站才可以将x轴上方的船只都覆盖到。

Input

第一行输入m表示有m组测试. 每组测试首先输入两个整数n(n<=10000)和d,接下来输入n个整数坐标(x,y),其中0≤y≤

Output

对每组测试数据输出最小总连线长度.

提示:

判断两点距离是否小于d可能需要考虑精度损失, 建议使用(x1-x2)×(x1-x2) + (y1-y2)×(y1-y2) - d×d <= 1e-10,而非(x1-x2)×(x1-x2) + (y1-y2)×(y1-y2) <= d×d

Sample Input

2

3 2

0 1

2 1

3 2

4 4

0 1

1 1

2 1

3 2

Sample Output

2

1

这是一个很好理解的贪心算法题,我立马想到,为了安放最少的基站使得覆盖所有的船只,先将船只按照其位置的x坐标从小到大排序,每次找到未覆盖船只中x坐标最小的那只,在它的右侧安放最远能覆盖它的一个基站,如此贪心下去能够保证安放基站最少。但是上面思路中有一处不对,关于排序船只要按照船只可允许的最远基站点位置的x坐标排序,这样才能做到安放最少。

#include<iostream>#include<math.h>#include<string.h>#include<stdio.h>#include<stdlib.h>using namespace std;const double minINF = 0.00000000001;//浮点误差bool isCover(double px,double py, double *cover);int cmp(const void* p1, const void* p2);int num;int m, n, d;double p[10001][2];//整数坐标(x,y),(n<=10000),n<0 已处理double cover[10001];//存放基站的位置(至多放n个,所以分配空间为n)int main(){cin >> m;for (int i = 0; i < m; i++){num = 0;//基站个数cin >> n >> d;//n个船只坐标,可以覆盖半径为d的区域for (int x = 0; x < n; x++)cin >> p[x][0] >> p[x][1];qsort(p, n, sizeof(double) * 2, cmp);//排序基站要按照他可允许的最远基站点位置排序(错在排序)for (int k = 0; k < n; k++)//遍历n个船只{if (!isCover(p[k][0], p[k][1], cover))//第k个船只没有被cover{cover[num] = p[k][0]+sqrt(d*d-p[k][1]*p[k][1]);//添加一个基站(存入位置)num++;}}cout << num << endl;}return 0;}bool isCover(double px, double py, double *cover){for (int i = 0; i < num; i++){double temp = (px - cover[i]) * (px - cover[i]) + py * py - d * d;if (temp <= minINF) return true;}return false;}int cmp(const void* p1, const void* p2){double temp = ((double*)p1)[0] + sqrt(d * d - ((double*)p1)[1] * ((double*)p1)[1]) -(((double*)p2)[0] + sqrt(d * d - ((double*)p2)[1]* ((double*)p2)[1]));if (-minINF <= temp && temp <= minINF)//浮点数比较注意预留一定的精度判断return 0;else if (temp < 0)return -1;elsereturn 1;}

1032 岛国难题

Time Limit: 1000 MS Memory Limit: 1000 KB

Description

小A是一个著名的桥梁工程师,然而这次小A接到了一个棘手的工程。J国是一个岛国,共由n座岛屿构成,这些岛屿通过n-1座桥梁互相连为一国,因此J国所有岛屿构成了一个树形结构。不幸的是,由于常年遭到海浪侵蚀,J国的每座桥梁均出现了一定程度的破损,每座桥梁有一定的概率Pi会坍塌,从而使得J国分裂为若干个部分。现在J国邀请小A帮忙修缮这些桥梁,他们提供给了小A这n-1座桥梁可能坍塌的概率Pi。为了对工程量做一个简单评估,小A需要计算出J国可能分裂为多少个部分的期望值。然而要命的是,J国忘记了提供J国的地图,然而时间已经不允许小A再向J国要地图,因此他认为J国n座岛屿可能的连接方式都是等概率的。显然这难倒了小A,因此他请你帮他计算在这样的条件下J国期望分裂为多少部分。

Input

第一行输入T(T<=10)表示有T组数据。每组数据先输入一个正整数N(1<=N<=1000),表示J国的岛屿

数目;然后紧跟着输入n-1个正整数Pi(0<=Pi<=100),表示每座桥梁有Pi%的概率坍塌。

Output

输出T行正整数,第i行表示第i组数据下J国可能分裂多少个部分的期望值,保留6位小数。

Sample Input

3

2 50

3 30 50

4 20 30 45

Sample Output

1.500000

1.800000

1.950000

这其实是一道算概率期望的数学题,为了求出分裂多少个部分的期望值,我们需要知道分裂k个部分的概率p是多少,然后用所有可能分裂情况下的p×k求和即得期望值,在算概率时,我发现这可以算是一道动规题,用p[i]记录第i座桥梁坍塌的概率,f[i][j]表示前i座桥断了j座的概率,那么按照第i座桥是否断了来分可得f[i][j]=f[i-1][j]×(1-p[i])+f[i-1][j-1]×p[i],思路其实很好理解,但是很荣幸Memory Exceeded了,后来经闺蜜提醒,可以循环数组(2行),因为每次求f[i][j]只需用到f[i-1]这一行的数据,吖咩,pass it!

#include<iostream>#include<math.h>#include<iomanip>#include<string.h>using namespace std;int main(){int T;cin >> T;for (int w = 0; w < T; w++){int N;double EX = 0.0;cin >> N;//1<=N<=1000int* p = new int[N];double** f = new double*[2];//f[i][j]前i座桥断了j座的概率for (int j = 0; j < 2; j++)f[j] = new double[N];//f[i][j]:j<=i时有效for (int j = 1; j < N; j++)cin >> p[j];//断桥的概率f[0][0] = 1.0;for (int i= 1; i <N; i++){f[i%2][0] = f[abs(i%2-1)][0] * (1 - p[i] / 100.0);f[abs(i%2-1)][i] = 0.0;for (int j = 1; j <= i; j++){f[i%2][j] = f[abs(i%2-1)][j] * (1 - p[i] / 100.0) + f[abs(i%2-1)][j - 1] * p[i] / 100.0;}}for (int i = 0; i < N; i++)EX += (i + 1) * f[abs(N%2-1)][i];cout<<fixed<<setprecision(6)<<EX << endl;delete []p;for (int j = 0; j < 2; j++)delete []f[j];delete []f;}return 0;}

1033 机器作业

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

有n个作业需要在一台机器上执行,一个时刻机器上只能执行一个作业,每个作业可在单位时间内完成,作业i有截止时间di,当作业i在截止时间被执行完,则可获得pi的收益。求最大收益。

Input

第一行输入T(T<=10)表示有T组数据。每组数据先输入一个正整数N(1<=N<=50000),表示共有N个作业,随后输入N组(di,pi),表示每个作业的截止时间和收益。

Output

输出T行正整数,第i行表示第i组数据下能获得的最大收益。

Sample Input

3

1

4 10

4

1 5

1 6

2 3

3 10

4

2 5

2 6

3 3

3 10

Sample Output

10

19

21

首先需要明确贪心贪的是利益,而不是截止时间,这个举个反例就可理解,不多赘述。因此,先将所有作业按利益排序,利益大的作业先安排,在将距离截止时间最近的可加工单位时间内安排机器执行完该作业,因为每个作业的加工时间都是一个单位时间,问题被简化,我用t[i]表示第i个单位时间机器是否被占用,isOK函数即判断是否能够安排该作业,具体实现见下:

//贪心算法#include<iostream>using namespace std;void QuickSort(long long*, long long* ,int, int);void Swap(long long&, long long&);bool isOK(int);int*t;int main(){int m, n;cin >> m;for (int i = 0; i < m; i++){int x, max = 0;long long mon = 0;cin >> n;long long* d = new long long[n], * p = new long long[n];for (x = 0; x < n; x++){cin >> d[x] >> p[x];if ((int)d[x] > max)max = (int)d[x]; //截止时间di}QuickSort(p,d, 0, n - 1);//按照作业利益pi大小从小到大排列t = new int[max+1];for (x = 0; x < max + 1; x++)t[x] = 0;for (int k = n-1; k >=0; k--)//N个作业{if (isOK((int)d[k]))mon += p[k];}cout <<mon << endl;}return 0;}bool isOK(int d){for (int i = d; i >= 1; i--)if (t[i] == 0) {t[i] = 1; return true; }return false;}void QuickSort(long long* a, long long*b ,int x, int y){if (x >= y)return;int pivot = a[x], pivotpos = x;for (int i = x; i <= y; i++){if (a[i] < pivot){pivotpos++;if (pivotpos != i)//小于基准的交换到左侧{Swap(a[pivotpos], a[i]);Swap(b[pivotpos], b[i]);}}}Swap(a[x], a[pivotpos]);Swap(b[x], b[pivotpos]);QuickSort(a, b,x, pivotpos - 1);QuickSort(a,b, pivotpos + 1, y);}void Swap(long long& x, long long& y){long long temp = x;x = y;y = temp;}

1035 生产安排

Time Limit: 3000 MS Memory Limit: 10000 KB

Description

某公司有个工厂和仓库。由于原材料等价格波动,工厂每个月的生产成本也会波动,令第𝑖个月产品的单位生产成本为𝑐_𝑖(该月生产一个产品的成本为𝑐_𝑖)。仓库储存产品的也有成本,假设每个月产品的单位储存成本为固定值1(存储一个产品一个月的成本为1)。令第𝑖个月需要供应给客户的产品数量为𝑦_𝑖,仓库里的和生产的产品均可供应给客户。假设仓库的容量无限大,供应给客户剩余的产品可储存在仓库中。若已知𝑛个月中各月的单位生产成本𝑐_𝑖、以及产品供应量𝑦_𝑖,设计一算法决策每个月的产品生产数量𝑥_𝑖,使得𝑛个月的总成本最低。

例如:𝑛=3,𝑐_𝑖:2,5,3,𝑦_𝑖:2,4,5,则𝑥_𝑖:6,0,5,即第1个月生产6个供应2个(代价2×2=4),储存4个供应给第2个月(代价(2+1)×4=12),第3个月生产5个供应5个(代价3×5=15),使总成本4+12+15=31最小。

Input

第一行输入T(T<=10)表示有T组数据。每组数据先输入一个正整数N(1<=N<=100000),表示共有N个月,接下来输入两行每行N个数,分别表示每个月的成本c_i以及产品需求量y_i。输入保证所有数据均在int范围内。

Output

输出T行正整数,第i行表示第i组数据下的最小成本。(输出不保证不溢出int)

Sample Input

1

3

2 5 3

2 4 5

Sample Output

31

这题我看题中例子时怎么都没想到它就是个非常典型的贪心问题(舍友一眼就看出,还是她手把手教我怎么理解的,太好了www),本题要求成本最低,即求出每个月的最低成本×该月需要的数量,就是成本最低。而每月的成本有两种:自己本月的生产成本上月的成本+存储的费用。(这里上月的成本已在之前循环中更新为最低的一种成本[可能是在上月之前余留的产品也可能是上月自己生产的])。

#include<iostream>#include<algorithm>using namespace std;int main(){int T;cin >> T;for (int i = 0; i < T; i++){int N; long long sum = 0;cin >> N;long long *c = new long long[N];long long *y = new long long[N];long long* w = new long long[N];for (int j = 0; j < N; j++){cin >> c[j]; w[j] = c[j];}for (int j = 0; j < N; j++)cin >> y[j];sum += w[0] * y[0];for (int j = 1; j < N; j++){w[j] = min( w[j],w[j - 1] + 1 );//1为存储一个产品一个月的成本sum += w[j] * y[j];}cout << sum << endl;}return 0;}

【网络流】

1037 非诚勿扰第一期(匈牙利算法

Time Limit: 3000 MS Memory Limit: 1000 KB

Description

火爆(曾经?)的相亲界扛把子节目非诚勿扰第一期开始了!第一期节目一共邀请了n位男嘉宾和m位女嘉宾,每位男嘉宾都有几个自己中意的女嘉宾。为了节目收视率,

导演组决定要让尽可能多的男嘉宾牵手成功!幸运的是这一期女嘉宾都是演员,她们答应只要男嘉宾愿意,她们一定会配合演出答应牵手。现在导演组拿到了每位男嘉

宾中意的女嘉宾名单,请问导演组最多可以让多少对荧幕情侣牵手成功。

Input

第一行输入T(T<=10)表示有T组数据。每组数据先输入两个正整数 n,m (n,m<=50),接下来n行每行先输入一个k,代表第i号男嘉宾中意的女嘉宾人数(k<=10),随后

输入k个数用空格分开,代表这名男嘉宾中意的女嘉宾编号名单。

Output

输出T行正整数,第i行表示第i组数据下的最多能有多少对男女嘉宾牵手成功。(输出保证不溢出int)

Sample Input

1

3 3

1 1

2 1 2

1 2

Sample Output

2

这题我直接好家伙。我对于题目应用背景还是比较惊叹滴,但是舍友一阵见血,这就是最大流问题,太经典了,一个模板就ok,我一找,原来匈牙利算法走起,还没学到网络流,写这题,我承认是被题干吸引的。我也是参考了其他博主的有趣博客,想必你们去学习一下也能明晰,贴上超链接。匈牙利算法趣讲

//二分图的最大匹配问题:匈牙利算法#include<iostream>using namespace std;int T, n, m, k;int key = 0;bool match[51][51], used[51];int girl[51];bool find(int x);int main(){cin >> T;for (int i = 0; i < T; i++){cin >> n >> m;//n位男嘉宾和m位女嘉宾(n,m<=50)for (int j = 1; j <= 50; j++)for (int w = 1; w <= 50; w++){match[j][w] = false; used[w] = false; girl[w] = 0;}int succ = 0, x;for (int j = 1; j <= n; j++)//每个男嘉宾中意的女嘉宾{cin >> k;for (int w = 0; w < k; w++){cin >> x; match[j][x] = true;//男嘉宾j中意女嘉宾x}}for (int j = 1; j <= n; j++){for (int w = 1; w <= m; w++)used[w] = false;//每个男嘉宾只需匹配一次女嘉宾即可if (find(j)) succ++;}cout << succ << endl;}return 0;}bool find(int x){for (int i = 1; i <= m; i++)//扫描每位女嘉宾{if (match[x][i] == true && used[i] == false){used[i] = true;if (girl[i] == 0 || find(girl[i])){girl[i] = x;return true;}}}return false;}

1038 非诚勿扰第二期(KM算法)

之后我趁热打铁,开启第二题,发现是最小费用最大流,同样有比较经典的KM算法,但是学习完之后,对于这题一直没过的了(tle你就是看不起我!!!),待我攻克!

【其他】

1015天际轮廓

Time Limit: 1000 MS Memory Limit: 5000 KB

Description

给定N(N<=50000)栋建筑物左右边界坐标及高度,输出这N栋建筑物天际轮廓的形状变化. 输入示例如下图所示。

Input

第一行输入N表示有N栋建筑物,接下来N行每行输入三个int型整数a、b、h,分别表示每栋建筑物的左右边界坐标和高度.

Output

输出若干行,每行输出两个数x、h,表示天际轮廓在坐标x处高度发生变化,变化为h.

Sample Input

8

1 5 11

2 7 6

3 9 13

12 16 7

14 25 3

19 22 18

23 29 13

24 28 4

Sample Output

1 11

3 13

9 0

12 7

16 3

19 18

22 3

23 13

29 0

这道题老师是有给示例答案的,但是我一看冗长的C语言就怯戚戚,自然就觉得还不如自己写,然后我就写出了一个数学问题(?)。我立马想到的便是,天际轮廓在坐标x处高度发生变化,那么坐标x和x+1处的高度或者x和x-1处的高度必然不同,so easy~,但是WA了一声,我似乎get到一个错误,作图如下:出现一种情况,坐标x和x+1处的高度一致,但是两个蓝色圈标出的点也是高度发生变化的,为解决此,奇思妙想一波,将天际轮廓放大一倍,纵然间隔为1的高度变化,也放大到间隔为2,上面的数学方法判断就不会出错啦,这题神奇地做出来还是有点小成就,因为我的代码真的比示例代码短了好多,噗呲!

#include<iostream>#include<algorithm>using namespace std;int x[300000];int l = 300000, r = 0;int main(){int T, a, b, h;cin >> T;for (int i = 0; i < T; i++){cin >> a >> b >> h;//表示每栋建筑物的左右边界坐标和高度.if (a < l)l = a;if (b > r)r = b;for (int j = a; j <= b; j++){if (h > x[j * 2])x[j * 2] = h;if (j < b)if (h > x[j * 2 + 1])x[j * 2 + 1] = h;}}for (int i = l; i <= r; i++){if (x[(i * 2) - 1] != x[i * 2])cout << i << " " << x[i * 2] << endl;if (x[i * 2 + 1] != x[i * 2])cout << i << " " << x[i * 2 + 1] << endl;}return 0;}

1039 拓扑排序

Time Limit: 2000 MS Memory Limit: 1000 KB

Description

输入一张有向图, 输出拓扑排序后的的结果.

Input

输入第一行是一个int型整数t,表示有t组测试数据。接下来每组测试数据第一行为两个整数n,m(n<=300,0<=m<=n*(n-1)/2).表示后面有n个节点(编号为1,2,…,n ),m条有向边. 后面的m行中每行有两个int型整数X和Y,表示X号点到Y号点有一条有向边,表示Y号点必须出现在X号点之后.

Output

对于每组数据:若没有可行的拓扑方案,输出0.若可行方案存在,输出任意一个可行方案(n个节点编号,编号间使用一个空格隔开)每组数据的输出以一个回车结尾.

Sample Input

2

2 2

1 2

2 1

3 3

3 1

2 3

2 1

Sample Output

0

2 3 1

这是离散(或者是数据结构)里讲过滴,该怎么构造拓扑排序呢?

非常简单,如下:

选一个没有直接前驱的顶点, 并输出之。从图中去该顶点, 同时,去所有它发出的有向边。重复以上1、2步, 直到全部顶点均已输出,拓扑有序序列形成,拓扑排序完成。

或者,图中还有未输出的顶点, 但已跳出处理循环(此时就是没有拓扑方案的情况啦)。

说明:图中还剩下一些顶点, 它们都有直接前驱。

结论:这时网络中必存在有向环。

#include<iostream>using namespace std;int T, n, m;bool d[301][301];int choose[301];bool isDelete[301];bool Canbechoose(int x);int main(){cin >> T;for (int i = 0; i < T; i++){cin >> n >> m;int x, y;for (int w = 0; w < 301; w++){isDelete[w] = false;choose[w] = 0;for (int j = 0; j < 301; j++)d[w][j] = false;}for (int j = 0; j < m; j++){cin >> x >> y; d[x][y] = true;}int k = 0, temp;for (int j = 0; j < n; j++)//n个节点{temp = k;for (int x = 1; x <= n; x++){if (Canbechoose(x) == true){isDelete[x] = true; choose[k] = x;k++; break;}}if (k == temp) {cout << 0 << endl; break; }}if (k != temp){for (int j = 0; j < n; j++){if (j != 0)cout << " ";cout << choose[j];}cout << endl;}}return 0;}bool Canbechoose(int x){if (isDelete[x] == true)return false;for (int i = 1; i <= n; i++)if (d[i][x] == true && isDelete[i] == false)return false;return true;}

下面两题是啃一下数据结构的老本啦!

1040 搜索二维矩阵

Time Limit: 1000 MS Memory Limit: 262144 KB

Description

编写一个高效的算法来搜索 m×n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

每行的元素从左到右升序排列。每列的元素从上到下升序排列。

Input

第一行输入nums表示有nums组测试;

每组测试输入m、n,target,分别表示矩阵的行列数以及目标值,接下来输入m * n的二维矩阵

Output

对每组测试数据输出 能否在矩阵中找到target

若能找到,输出true

若找不到,输出false

提示: m <= 1000 n <= 1000

Sample Input

1

5 5 5

1 4 7 11 15

2 5 8 12 19

3 6 9 16 22

10 13 14 17 24

18 21 23 26 30

Sample Output

true

#include<iostream>using namespace std;int nums, n, m,target;int main(){cin >> nums;for (int i = 0; i < nums; i++){cin >> m >> n >> target;if (m <= 0 || n <= 0)cout << "false" << endl;//cout << boolalpha << false << endl;else {int** a = new int* [m];for (int j = 0; j < m; j++)a[j] = new int[n];for (int j = 0; j < m; j++)for (int w = 0; w < n; w++)cin >> a[j][w];int p1 = 0, p2 = n - 1, key = 0;while (p1 <= m - 1 && p2 >= 0){if (a[p1][p2] == target) {key = 1; cout << "true" << endl;//cout << boolalpha << true<< endl;break; }if (a[p1][p2] > target) p2--;if (a[p1][p2] < target)p1++;}if (key == 0)cout << "false" << endl;//cout << boolalpha << false << endl;}}return 0;}

1041 寻找两个正序数组的中位数

Time Limit: 1000 MS Memory Limit: 262144 KB

Description

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。

请你找出并返回这两个正序数组的中位数。

进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

Input

第一行输入nums表示有nums组测试

每组测试输入n和m,分别表示数组nums1和nums2的长度

然后输入正序数组nums1

接着输入正序数组nums2

Output

对每组测试数据输出两个正序数组的中位数

提示:

nums1.length == n

nums2.length == m

0 <= m <= 1000

0 <= n <= 1000

1 <= m + n <= 2000

-10^6 <= nums1[i], nums2[i] <= 10^6

Sample Input

2

2 1

1 3

2

2 2

1 2

3 4

Sample Output

2.00000

2.50000

这题的bug在于网站上样例输出保留五位小数,而不保留时才可AK。

#include<iostream>#include <iomanip>using namespace std;int nums, n, m;int main(){cin >> nums;for (int i = 0; i < nums; i++){cin >> n >> m;double *nums1 = new double[n],*nums2= new double[m];for (int j = 0; j < n; j++)cin >> nums1[j];for (int j = 0; j < m; j++)cin >> nums2[j];int p1 = 0, p2 = 0, k = 0;double key;double* num = new double[m + n];while (p1 <= n- 1 && p2 <= m-1){if (nums1[p1] < nums2[p2]) {num[k] = nums1[p1]; p1++;}else {num[k] = nums2[p2]; p2++;}k++;}while(p1<=n-1){num[k] = nums1[p1]; k++; p1++;}while (p2 <= m - 1){num[k] = nums2[p2]; k++; p2++;}if ((m + n) % 2 == 0)key = (num[(m + n) / 2 - 1] + num[(m + n) / 2]) / 2.0; elsekey = num[(m + n) / 2];cout << key << endl;//cout <<fixed<<setprecision(5)<< key << endl;}return 0;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。