从上课到结束课程,感觉时间一会儿就过去了,从开始简单的递归到之后的二分、数论。原本觉得异常困难的问题,也开始逐渐了解,学习。收获了挺多,庆幸自己没有退出。
递归练习:
编程语言中,函数直接或间接调用函数本身,则该函数称为递归函数。
主题思想就是由繁入简一步一步逐层分析。
比较典型的为上楼梯问题:
爬楼梯
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
using namespace std;
int f(int n)
{
if (n == 1)return 1;
if (n == 2)return 2;
if (n > 2)return f(n - 1)+f(n-2);
}
int main()
{
int i;
while(cin>>i)
cout << f(i)<<endl;
}
需要注意边界条件,判断好边界条件,应该就没有太大问题。
贪心练习:
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
这个部分简单的题挺好做,但是部分题思路确实不好想。因为不是求全局最优解,而是局部最优解,所以有时思考的方向要想好。
做这部分题的关键是读懂题意,想好怎么贪,如何贪可以最大效率解决问题。
列举:
较简单的贪心问题,求最少拿多少枚硬币可以比另外一个姐妹总金额要大,可以先求出总金额,将硬币面额降序排列,当拿到超过总数1/2的时候,停止,输出。
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int cnt,sum,sum2;
bool camp1(int x,int y){
return x>y;}
int main()
{
int n,a[10005];
cin>>n;
for(int i=0;i<n;i++)
{cin>>a[i];
sum+=a[i];
}
sort(a,a+n,camp1);
for(int i=0;i<n;i++)
{
sum2+=a[i];
cnt++;
if(sum2>sum/2)
break;
}
cout<<cnt;
}
动态规划:
动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。可以解决背包问题、生产经营问题、资金管理问题、资源分配问题、最短路径问题和复杂系统可靠性问题。
动态规划问题基本特征:
- 问题具有多阶段决策的特征。
- 每一阶段都有相应的“状态”与之对应,描述状态的量称为“状态变量”。
- 每一阶段都面临一个决策,选择不同的决策将会导致下一阶段不同的状态。
- 每一阶段的最优解问题可以递归地归结为下一阶段各个可能状态的最优解问题,各子问题与原问题具有完全相同的结构。
动态规划问题概念:
- 阶段:据空间顺序或时间顺序对问题的求解划分阶段。
- 状态:描述事物的性质,不同事物有不同的性质,因而用不同的状态来刻画。对问题的求解状态的描述是分阶段的。
- 决策:根据题意要求,对每个阶段所做出的某种选择性操作。
状态转移方程:用数学公式描述与阶段相关的状态间的演变规律。
解题步骤:
1、判断问题是否具有最优子结构性质,若不具备则不能用动态规划。
2、把问题分成若干个子问题(分阶段)。
3、建立状态转移方程(递推公式)。
4、找出边界条件。
5、将已知边界值带入方程。
6、递推求解。
例题:
#include<cstdio>
#include<string>
#include<algorithm>
#include<math.h>
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn = 2010;
int dp[1050];
int mp[1050][1050];
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<=i;j++)
cin>>mp[i][j];
memset(dp,0,sizeof(dp));
for(int i=n-1;i>=0;i--)
for(int j=0;j<=i;j++)
dp[j]=max(dp[j],dp[j+1])+mp[i][j];
cout<<dp[0];
return 0;
}
关于以上:
*最大字段和问题!
关于最大字段和问题对于我来说,一直是较难的题,思路不太明确,因此查阅了一些资料,有以下四种解法:
1、暴力解题:
枚举左右区间然后遍历该区间求解,时间复杂度O(n3)。
#include<bits/stdc++.h>
using namespace std;
int a[] = { 2,6,-1,4,-2,3,-2,3,-100 };
int ans;
int solve(int a[], int n)
{
int sum, SUM;
SUM = 0;
for (int i = 0;i < n;i++)
{
for (int j = i;j <= n;j++)
{
sum = 0;
for (int k = i;k < j;k++)
sum += a[k];
if (sum > SUM)
SUM = sum;
}
}
return SUM;
}
int main()
{
ans = solve(a, 9);
cout << ans << endl;
return 0;
}
2.优化后的暴力解题:
2重循环,复杂度 O( n² )
#include<bits/stdc++.h>
using namespace std;
int a[] = { -2,11,-4,13,-5,-2,-100 };
int ans;
int solve(int a[],int n)
{
int SUM=0;
for (int i = 0;i < n;i++)
{
int sum = 0;
for (int j = i;j < n;j++)
{
sum += a[j];
if (sum > SUM)
SUM = sum;
}
}
return SUM;
}
int main()
{
ans = solve(a, 9);
cout << ans << endl;
return 0;
}
3.分治法
将数组一分为二,左边右边,整个数组最大的和可能是左半边最大和 也可能是右半边最大和 还有可能是跨越边界最大和 ,时间复杂度O(nlogn)。
int solve(int a[], int x, int y)
{
if(y - x == 1)
return a[x];
int m = (x + y) / 2;
int maxsum = max(solve(a, x, m), solve(a, m, y));
int v = 0, L = a[m - 1], R = a[m];
for(int i = m - 1; i >= x; i--)
L = max(L, v += a[i]);
v = 0;
for(int i = m; i < y; i++)
R = max(R, v += a[i]);
return max(maxsum, L + R);
}
4.动态规划
int max(int a[],int n)
{
int sum=0,maxsum=0;
int i ;
for(i = 0;i<n;i++)
{
sum +=a[i];
if(sum>maxsum)
maxsum = sum;
else
if(sum<0)
sum = 0;
}
return maxsum;
}
算法复杂度为O(n)
背包问题:
背包问题(Knapsack problem)是一种组合优化的NP完全问题。
01背包题目
有N件物品和一个容量为V的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
基本思路
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:
f[i][v]=max{ f[i-1][v], f[i-1][v-w[i]]+v[i] }。
可以压缩空间,f[v]=max{f[v],f[v-w[i]]+v[i]}
完全背包:
题目
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
基本思路
这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i,v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:f[i,v]=max{f[i,v-vi]+wi,f[i-1,v]}。这跟01背包问题一样有O(N*V)个状态需要求解,但求解每个状态的时间则不是常数了,求解状态f[v]的时间是O(v/c),总的复杂度是超过O(VN)的。
#include<iostream>
using namespace std;
int findM(int N,int K,int G[],int W[])
{ int *M=new int[N+1],i,j,k;
for(i=0;i<N+2;i++)
M[i]=0;
for(i=0;i<K;i++)
{
for(j=N;j>=G[i];j--)
{
for(k=1;j-k*G[i]>=0;k++)
{
M[j]=M[j]>k*W[i]+M[j-k*G[i]]?M[j]:k*W[i]+M[j-k*G[i]];
}
}
}
return M[N];
}
int main(){
int N,K,i;
while(cin>>N>>K)
{
int *G=new int[K];
int *W=new int[K];
for(i=0;i<K;i++)
cin>>G[i]>>W[i];
cout<<findM(N,K,G,W)<<endl;
delete []G;
delete []W;
}
return 0;
}
多重问题
题目
有N种物品和一个容量为V的背包。第i种物品最多有n件可用,每件体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
基本算法
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n+1种策略:取0件,取1件……取 n件。令f[v]表示前i种物品恰放入一个容量为v的背包的最大权值,则:f[v]=max{f[v-kc]+ kw|0<=k<=n}。复杂度是O(V*∑n)。
二分法:
算法:
二分法查找适用于数据量较大时,但是数据需要先排好顺序。
主要思想是:
(设查找的数组区间为array[low, high])
(1)确定该区间的中间位置K(2)将查找的值T与array[k]比较。若相等,查找成功返回此位置;否则确定新的查找区域,继续二分查找。
时间复杂度为:O(log2n)。
Tips:
register: 寄存器,可以提高速度。
freopen: 头文件:<stdio.h>
可以用于测试数据。
upper_bound和 lower_bound
-
upper_bound(begin,end,value);
返回>value的元素的第一个位置 -
lower_bound(begin,end,value);
返回>=value的元素的第一个位置num[]={1,2,2,3,4,5};
-
lower_bound(num,num+6,2)为num+1
-
upper_bound(num,num+6,2) 为num+3
-
应为有序队列,返回的为地址值,若找不到返回值为-1.
set和multiset的应用*
- s.insert(elem)--安插一个elem副本,返回新元素位置。
- s.erase(elem)--移除与elem元素相等的所有元素。返回被移除的元素个数。
- s.srase(pos)--移除迭代器pos所指位置上的元素,无返回值。
- s.clear()--移除全部元素,将整个容器清空。
****迭代器例子:****
- multiset<int>::iterator pos;
for(pos=s.begin();pos!=s.end();pos++)
- s.size()--返回容器大小。
- s.empty()--返回容器是否为空。
- s.count(elem)--返回元素值为elem的元素的个数。
----迭代器
- s.begin()–返回一个双向迭代器,指向第一个元素。
- s.end()–返回一个双向迭代器,指向最后一个元素的下一个位置。
map和multimap的应用
头文件:#include<map>
定义:map<data type1,date type2>map_name;
如:map<string ,int>默认为按string 由小到大排序
m.size()返回容器大小
m.empty()返回容器是否为空
m.count(key)返回值等于key的元素个数
-
m.upper_bound(begin,end,value);
返回等于k值的元素的第一个可安插的位置 -
lower_bound(begin,end,value);
返回等于k值的元素的最后一个可安插的位置
小总结:
并不后悔加入acm,尽最可能去学好,去做好每一件事,既然是自己选择的路,就要一步一步走过去,踏踏实实才是王道。
耳边还在回向老师说的那句话:生死看淡,不服就干!