ACM课程报告

一、对课程的认识和自我感受
从入学开始到现在也有两个学期了,在刚入学那会,就有计算机的学长在群里说ACM的事情,他们说如果可以拿个省奖,那么毕业后工作或者是保研考研都行容易,也许是被这些利益好处所驱使,我对ACM有了兴趣。之后又听说,ACM是我们班主任费老师搞的,我心中对它ACM的期望有高了几分。
入学后,加入了ACM协会,开始听学长学姐讲ACM的知识,听了之后才发现,ACM好难,各种算法思路,宛如听天书一样。但还好多多少少能听懂一点,再加上自己学习信息学奥赛一本通,勉强还能跟上。之后就有了ACM新生赛,我参加了,成绩还可以得了个二等奖,让我对自己能做ACM有了信心。
寒假里,一直在纠结要不要选费老师的ACM课,一是因为自己当时想去蹭课,不想考试,毕竟费老师的考试可不是简简单单就能过的。二是对自己缺乏信心,不确定自己能学好。三是寒假里自己比较懒,不想多做一些东西。但自己终归选了费老师的课,搏一搏单车变摩托,怀着搏一搏的心思选上了费老师的课。但是在寒假里,费老师让每天写一篇博客,自己开始时还能坚持每天晚上学一学,写一写,但是几天之后,寒假惰性来了,啥都不想干就想玩,然后再也没有坚持下去。
费老师的ACM课是很好的,能学很多知识,但是也需要付出更多的时间。ACM课会讲很多内容,又多又难,需要提前预习,和及时课下复习。刚开始时,课程难度不是太难,我学起来还可以,还是可以听懂会做题的。到后来,我觉得难度变大了,一些算法不是太好理解,再加之没有及时复习预习,导致自己逐渐听不懂课程,这样几周下去,自己便有了放弃的打算。一是因为自己真的有点跟不上了。二是自己学习能力是有的,但是学习时间比较长,学习效率低下,要学课程太多太难(高数、电子、大物),自己很难兼顾。三是自己也学了一些关于ACM的知识:队列(queue)、优先队列(priority_queue)、set、multiset、map、multimap、vector、贪心算法、动态规划、区间dp、背包问题(01背包、完全背包、多重背包、分组背包)、递归函数、搜索(广搜(队列)、深搜(递归、栈)、数据结构(堆、队列、树及二叉树)、图论算法(图的遍历、基本概念、最短路径算法、图的连通性问题、并查集、最小生成树(PRIM算法、KRUSKAL算法)、拓扑排序算法)、树状数组(单点更新、区间查询;区间更新,单点查询;区间更新,区间查询)、线段树(单点更新、成段更新、区间合并、扫描线)等等这些知识,觉得受益匪浅。
总而言之,觉得自己没有能坚持下来的毅力和足够充分的时间、强大的学习能力,所以止步于此。
二、例题解析
1、Ugly numbers are numbers whose only prime factors are 2, 3 or 5. The sequence
1, 2, 3, 4, 5, 6, 8, 9, 10, 12, …
shows the first 10 ugly numbers. By convention, 1 is included.
Given the integer n,write a program to find and print the n’th ugly number.
Input
Each line of the input contains a postisive integer n (n <= 1500).Input is terminated by a line with n=0.
Output
For each line, output the n’th ugly number .:Don’t deal with the line with n=0.
方法一:优先队列
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
long long count=0,n;
const int b[3]={2,3,5};
priority_queue<long long,vector,greater>p;
sets;
set::iterator t;
s.insert(1);
p.push(1);
while(1)
{
long long x=p.top();
p.pop();
count++;
if(count1500)
break;
long long a;
for(int i=0;i<3;i++)
{
a=xb[i];
if(s.count(a)0)
{
s.insert(a);
p.push(a);
}
}
}
t=s.begin();
while(cin>>n)
{
if(n
0)
break;
else
{
for(int j=0;j<n-1;j++)
{
t++;
}
cout<<t<<endl;
t=s.begin();
}
}
}
方法二:暴力
#include
#include
using namespace std;
int main()
{
int m2=0;
int m3=0;
int m5=0;
long long a[1500];
a[0]=1;
long long tmp;
for(int i=1;i<1500;i++)
{
//分别给上一次比较得到的数乘相应的倍数再比较
tmp=2
a[m2]>3
a[m3]?3
a[m3]:2a[m2];
tmp=tmp>5
a[m5]?5*a[m5]:tmp;
a[i]=tmp;//比较得到的较小数存入数组
//满足条件的m加1,指向新得到的小数
if(tmp
2a[m2]) m2++;
if(tmp==3
a[m3]) m3++;
if(tmp5*a[m5]) m5++;
}
for(int i=0;i<1500;i++)
cout<<a[i]<<" ";
return 0;
}
2、贪心问题,贪时间,先比较结束时间,小的在前面排序,如果相同,在比较开始时间,小的在前面,这样省时间。
Input
输入数据包含多个测试实例,每个测试实例的第一行只有一个整数n(n<=100),表示你喜欢看的节目的总数,然后是n行数据,每行包括两个数据Ti_s,Ti_e (1<=i<=n),分别表示第i个节目的开始和结束时间,为了简化问题,每个时间都用一个正整数表示。n=0表示输入结束,不做处理。
Output
对于每个测试实例,输出能完整看到的电视节目的个数,每个测试实例的输出占一行。
#include
#include
#include
#include
#include
#include
using namespace std;
struct mmp
{
int kai;
int end;
}a[105];
bool cmp(mmp a,mmp b)
{
if(a.end!=b.end)
return a.end<b.end;
return a.kai<b.kai;
}
int main()
{
int n;
while(cin>>n)
{
int count=1;
if(n
0)
break;
for(int i=0;i<n;i++)
{
cin>>a[i].kai>>a[i].end;
}
sort(a,a+n,cmp);
for(int j=0,k=j;j<n,k<n-1;)
{
if(a[j].end<=a[k+1].kai)
{
count++;
j=k+1;
k++;
}
else
{
k++;
}
}
cout<<count<<endl;
}
}
3、馅饼题,两种解法,一是数塔,一是区间dp
都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼。说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内。馅饼如果掉在了地上当然就不能吃了,所以gameboy马上卸下身上的背包去接。但由于小径两侧都不能站人,所以他只能在小径上接。由于gameboy平时老呆在房间里玩游戏,虽然在游戏中是个身手敏捷的高手,但在现实中运动神经特别迟钝,每秒种只有在移动不超过一米的范围内接住坠落的馅饼。
为了使问题简化,假设在接下来的一段时间里,馅饼都掉落在0-10这11个位置。开始时gameboy站在5这个位置,因此在第一秒,他只能接到4,5,6这三个位置中其中一个位置上的馅饼。问gameboy最多可能接到多少个馅饼?(假设他的背包可以容纳无穷多个馅饼)
Input
输入数据有多组。每组数据的第一行为以正整数n(0<n<100000),表示有n个馅饼掉在这条小径上。在结下来的n行中,每行有两个整数x,T(0<T<100000),表示在第T秒有一个馅饼掉在x点上。同一秒钟在同一点上可能掉下多个馅饼。n=0时输入结束。
Output
每一组输入数据对应一行输出。输出一个整数m,表示gameboy最多可能接到m个馅饼。
提示:本题的输入数据量比较大,建议用scanf读入,用cin可能会超时。
方法一: 树塔
#include
#include
#include
using namespace std;
int maxi(int a,int b,int c)
{
int max1;
max1=a>b?a:b;
max1=max1>c?max1:c;
return max1;
}
int c[100001][11];
int main()
{
int i,j;
int n,a,b;
while(cin>>n&&n)
{
int m=0;
memset(c,0,sizeof©);
for(i=0;i<n;i++)
{
cin>>a>>b;
c[b][a]++;
if(m<b)
m=b;
}
for(i=m-1;i>=0;i–)
{
for(j=1;j<=9;j++)
c[i][j]+=maxi(c[i+1][j-1],c[i+1][j],c[i+1][j+1]);
c[i][0]+=max(c[i+1][0],c[i+1][1]);
c[i][10]+=max(c[i+1][10],c[i+1][9]);
}
cout<<c[0][5]<<endl;
}return 0;
}
方法二:区间dp
#include
using namespace std;
const int MAXN = 100005;
int n, a[MAXN][15], dp[MAXN][15];
int main()
{
while (~scanf_s("%d", &n) && n)
{
memset(dp, 0, sizeof(dp));
memset(a, 0, sizeof(a));
int T = 0;
for (int i = 0; i < n; i++)
{
int x, t; scanf_s("%d%d", &x, &t); x++;
a[t][x]++;
T = max(T, t);
}
for (int i = T; i >= 0; i–)
{
for (int j = 1; j <= 11; j++)
{
dp[i][j] = max(dp[i+1][j-1], max(dp[i+1][j], dp[i+1][j+1]));
dp[i][j] += a[i][j];
}
}
printf("%d\n", dp[0][6]);
}
return 0;
}
三、知识点
1、STL
栈:头文件: #include
定义:stack<data_type> stack_name;
如:stack s;
操作:
empty() – 返回bool型,表示栈内是否为空 (s.empty() )
size() – 返回栈内元素个数 (s.size() )
top() – 返回栈顶元素值 (s.top() )
pop() – 移除栈顶元素(s.pop(); )
push(data_type a) – 向栈压入一个元素 a(s.push(a); )
队列:头文件: #include
定义:queue <data_type> queue_name;
如:queue q;
操作:
empty() – 返回bool型,表示queue是否为空 (q.empty() )
size() – 返回queue内元素个数 (q.size() )
front() – 返回queue内的下一个元素 (q.front() )
back() – 返回queue内的最后一个元素(q.back() )
pop() – 移除queue中的一个元素(q.pop(); )
push(data_type a) – 将一个元素a置入queue中(q.push(a); )
vector:头文件: #include
定义:vector <data_type> vector_name;
如:vector v;
操作:
empty() – 返回bool型,表示vector是否为空 (v.empty() )
size() – 返回vector内元素个数 (v.size() )
push_back(data_type a) 将元素a插入最尾端
pop_back() 将最尾端元素删除
v[i] 类似数组取第i个位置的元素(v[0] )
set、multiset:头文件: #include
定义:set <data_type> set_name;
如:set s;//默认由小到大排序
操作:
s.insert(elem) – 安插一个elem副本,返回新元素位置。
s.erase(elem) – 移除与elem元素相等的所有元素,返回被移除 的元素个数。
s.erase(pos) – 移除迭代器pos所指位置上的元素,无返回值。
s.clear() – 移除全部元素,将整个容器
操作:
s.size() – 返回容器大小。
s.empty() – 返回容器是否为空。
s.count(elem) – 返回元素值为elem的元素的个数。
s.lower_bound(elem) – 返回 元素值>= elem的第一个元素位置。
s.upper_bound(elem) – 返回元素值 > elem的第一个元素的位置
s.begin() – 返回一个双向迭代器,指向第一个元素。
s.end() – 返回一个双向迭代器,指向最后一个元素的下一 个位置
map、multimap:头文件: #include
定义:map <data_type1, data_type2> map_name;
如:map <string, int> m;//默认按string由小到大排序
m.size() 返回容器大小
m.empty() 返回容器是否为空
m.count(key) 返回键值等于key的元素的个数
m.lower_bound(key) 返回键值等于key的元素的第一个可安插 的位置
m.upper_bound(key) 返回键值等于key的元素的最后一个可安 插的位置
2、贪心算法,是一种思路
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,希望得到结果是最好或最优的算法。
贪心算法是一种能够得到某种度量意义下的最优解的分级处理方法,通过一系列的选择得到一个问题的解,而它所做的每一次选择都是当前状态下某种意义的最好选择。即希望通过问题的局部最优解求出整个问题的最优解。
这种策略是一种很简洁的方法,对许多问题它能产生整体最优解,但不能保证总是有效,因为它不是对所有问题都能得到整体最优解。
3、动态规划,把大问题分化成小问题
动态规划问题的一般解题步骤
①判断问题是否具有最优子结构性质,若不具备则不能用动态规划。
②把问题分成若干个子问题(分阶段)。
③建立状态转移方程(递推公式)。
④找出边界条件。
⑤将已知边界值带入方程。
⑥递推求解。
4、背包问题
01背包: 有N件物品和一个容量为V的背包。(每种物品均只有一件)第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
for i=1…N
for v=v…0
f[v]=max{f[v],f[v-c[i]]+w[i]};
完全背包: 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
for i=1…N
for v=0…V
f[v]=max{f[v],f[v-cost]+weight}
多重背包: 有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
for (int i = 1; i <= n; i++)
for (int k = 1; k <= cot[i];k++)
for (int j = 10; j >= w[i]; j–)
dp[j] = max(dp[j], dp[j - w[i]] + v[i])
四、分组背包:有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
for(int i = 1; i <= z; i++)
for(int j = V; j >= 1; j–)
for(int k = 1; k <= n; k++)
dp[j] = max(dp[j - w[i][k]] + p[i][k], dp[j]);
z是分组数,V是背包体积,n是每组物品数量,w和p分别是第i组第k个物品的体积
5、区间dp
①、当求最小dp时
//一般区间DP实现代码
memset(dp, 0x3f, sizeof(dp));
//初始化dp
//0x3f在此表示一个很大很大的值
for (int i = 1; i <= n; i++) //区间长度为1的初始化
dp[i][i] = 0;
for (int len = 2; len <= n; len++) //枚举区间长度
{
for (int i = 1, j = len; j <= n; i++, j++) //区间[i,j]
{
//DP方程实现
}
}
②、求最大dp时
//一般区间DP实现代码
memset(dp, 0, sizeof(dp));
//初始化dp
//赋值给最小值
for (int i = 1; i <= n; i++) //区间长度为1的初始化
dp[i][i] = 0;
for (int len = 2; len <= n; len++) //枚举区间长度
{
for (int i = 1, j = len; j <= n; i++, j++) //区间[i,j]
{
//DP方程实现
}
}
6、递归函数
递归思想把一个不能或不好解决的大问题转化为一个或几个小问题,再把这些小问题进一步分解成更小的小问题,然后最小问题可以直接解决。
其中,要注意递归的边界和终止条件
7、搜索
分为广搜和深搜
①广度搜索可用队列来写,先进先出
具体过程:
1 每次取出队列首元素(初始状态),进行拓展
2 然后把拓展所得到的可行状态都放到队列里面
3 将初始状态删除
4 一直进行以上三步直到队列为空。
②深度搜索先进后出,用栈来写
具体实现过程:
1 每次取出栈顶元素,对其进行拓展。
2 若栈顶元素无法继续拓展,则将其从栈中弹出。继续1过程。
3 不断重复直到获得目标状态(取得可行解)或栈为空(无解)。
③深度搜索常用递归来写,要善用记忆化搜索,即使用一个数组存已经搜索过的,要记住改变状态后,再回复原状态
7、单调队列
①插入:若新元素从队尾插入后会破坏单调性,则删除原来的队尾元素,直到插入后不再破坏单调性为止,再将其插入单调队列。(一般用两个指针指位置,front=0指队首,rear指末尾的指针)
(核心代码:
int rear=-1,front=0;//r为末尾的指针,f为队首的
for(int i=0;i<n;i++){
scanf("%I64d",&a);
while(rear>=front&&q[rear]<=a)rear–;//单调递减序列
q[++rear]=a;
ans+=rear;
)
②获取最优值:访问首尾元素。
8、二分法
二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部。
9、树及二叉树
树的概念----树的定义
一棵树是由n(n>0)个元素组成的有限集合,其中:
①每个元素称为结点
②有一个特定的结点,称为根结点或树根
③除根结点外,其余结点能分成m(m>=0)个互不相交的有限集合T0,T1,T2,……Tm-1。其中的每个子集又都是一棵树,这些集合称为这棵树的子树。
二叉树
首先二叉树的每个结点至多只能有两个结点,二叉树可以为空,二叉树一定是有序的,通过它的左、右子树关系体现出来。
一棵深度为k且有2k–1个结点的二叉树称为满二叉树。这种树的特点是每层上的结点数都是最大结点数。
深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树
二叉树的存储结构
①链式存储结构,即单链表结构或双链表结构(同树)
②顺序存储结构,即几个数组加一个指针变量。
遍历二叉树的方法
先序遍历、中序遍历、后续遍历
10、图论算法
有向图:图的边有方向,只能按箭头方向从一点到另一点。
无向图:图的边没有方向,可以双向。
结点的度:无向图中与结点相连的边的数目,称为结点的度。
结点的入度:在有向图中,以这个结点为终点的有向边的数目。
结点的出度:在有向图中,以这个结点为起点的有向边的数目。
权值:边的“费用”,可以形象地理解为边的长度。
图的遍历分为深度优先遍历和广度优先遍历两种方法
11、树状数组
像树一样
数字代表对应数组的第几个元素,下排是a数组,其上方的是e数组,最下的二进制则是对应编号的二进制表示.
注意观察:
1.a的每个元素至多仅被E的一个元素包含,这点和树有很大相同,但整体并不是树
2.每个ei可认为是仅包含ai和其它若干个e元素
3.每个ei包含的元素数目(不包括ai在内)为i的二进制表示中末位连续的0的个数
12、线段树
线段树的本质是一棵二叉树,线段树的每一个节点记录是一段区间的信息
对于任一非叶子节点,若该区间为[L,R],则
左儿子为[L,(L+R)/2]
右儿子为[(L+R)/2+1,R]
建树
void build(int id,int l,int r){
tree[id].left=l; tree[id].right=r;
if (lr)
{
tree[id].sum=a[l];
tree[id].max=a[l];
}
else
{
int mid=(l+r)/2;
build(id2,l,mid);
build(id
2+1,mid+1,r); tree[id].sum=tree[id2].sum+tree[id2+1].sum; tree[id].max=max(tree[id2].max,tree[id2+1].max;
}
}
更新
void update(int id,int pos,int val){
if (tree[id].left
tree[id].right)
{
tree[id].sum=tree[id].max=val;
}
else
{
int mid=(tree[id].left+tree[id].right)/2;
if (pos<=mid) update(id2,pos,val);
else update(id
2+1,pos,val); tree[id].sum=tree[id2].sum+tree[id2+1].sum; tree[id].max=max(tree[id2].max,tree[id2+1].max)
}
}
查询
void query(int id,int l,int r){
if (tree[id].leftl&&tree[id].rightr)
return tree[id].sum; //询问总和
else
{
int mid=(tree[id].left+tree[id].right)/2;
if (r<=mid) return query(id2,l,r);
else if (l>mid) return query(id
2+1,l,r)
else
return query(id2,l,mid)+query(id2+1,mid+1,r);
}
}

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值