数据结构
老师建议:可以使用STL的数据结构尽量全部都用STL。
栈
- 火车的进出站问题(爆搜, 递推, 动态规划, 卡特兰数)
- 后缀表达式, 中缀表达式,前缀表达式;
- 中缀表达式转后缀表达式, 后缀表达式转中缀表达式
- 单调栈
队列
- 双端队列(deque)
- 双端队列优化SPFA
链表
链表的本质:相邻的元素相连接。
一般是给出两两元素之间的前后关系,要求恢复原来的序列。
表头是只出现一次的数字,那么表尾就是连接有none的数字。
例如下图:
邻接表的使用
【NOIP2017提高组初赛】(交朋友)根据社会学研究表明,人们都喜欢找和自己身高相近的人做朋友。 现在有 名身高两两不相同的同学依次走入教室,调查人员想预测每个人在走入教室的瞬间最想和已经进入教室的哪个人做朋友。当有两名同学和这名同学的身高差一样时,这名同学会更想和高的那个人做朋友。比如一名身高为 1.80 米的同学进入教室时,有一名身高为 1.79 米的同学和一名身高为1.81米的同学在教室里,那么这名身高为 1.80 米的同学会更想和身高为1.81米的同学做朋友。对于第一个走入教室的同学我们不做预测。 由于我们知道所有人的身高和走进教室的次序,所以我们可以采用离线的做法来解决这样的问题,我们用排序加链表的方式帮助每一个人找到在他之前进入教室的并且和他身高最相近的人。
我们可以把全部输入,之后离线操作;
针对于身高进行排序,建一个链表,这样的话,针对于每一个数字,那么他的答案就是他的左右两边的的人身高,所以,当我们找到此时要进来的人,之后找到他的答案,我们再把这个点删去(因为是倒序查找)。
重点:链表的维护。
并查集
Codeforce 741B 并查集+分组背包 代码和注意事项在文章的最后。
我的思路:
- 我们可以用并查集把所有的人分成K组;
- 因为题目中要求的是针对于一个组而言,我们只能全组选一个,或者是全组都选,或者是全组都不选,所以我们选用的是分组背包做题;
- 这样子的话又会和分组背包有一些不一样,因为分组背包是在每一个组里面至多选出一个数字,而没有选择整组的情况;
- 所以,我们可以在状态的转移的时候把整组转移的时候考虑到比较中。
hash 表
维护映射关系的数据结构
例子:给出若干个膜法师的编号,
一开始我们可以用一个很大很大的数组标记该位膜法师,但是这样的话数组的下标就过于庞大。
之后,我们就考虑优化,我们可以把该位膜法师的编号%一个大质数。
对于Hash碰撞问题的解决方法:
- 考虑把质数变大。
- 拉链法:……
UNordered函数的用法,可以查找该数字是否存在于一个序列中。
为什么要mod一个质数?
因为如果mod一个质数的话,模数重复的几率会大大减小,所以,我们考虑mod大质数。
下一节课讲线段树,
刘神说:线段树的动态开点一定要熟练掌握。
code
Codeforce 741B 并查集+分组背包
#include<bits/stdc++.h>
using namespace std;
const int nn=1003;
int n, m, W;
int w[nn], b[nn], fa[nn];
vector<int> c[nn];
int sum1[nn], sum2[nn];/*表示的是每一组的重量总和以及价值总和*/
int vis[nn], cnt=0;
int f[nn];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
int gf(int x)
{
if(fa[x]==x) return x;
return fa[x]=gf(fa[x]);
}
int main()
{
n=read(), m=read(), W=read();
for(int i=1;i<=n;++i) w[i]=read(), fa[i]=i;
for(int i=1;i<=n;++i) b[i]=read();
for(int i=1;i<=m;++i)
{
int x=read(), y=read();
int xx=gf(x), yy=gf(y);
if(xx==yy) continue;
fa[xx]=yy;
}
for(int i=1;i<=n;++i)
{
int x=gf(i);
if(vis[x]) c[vis[x]].push_back(i), sum1[vis[x]]+=w[i], sum2[vis[x]]+=b[i];
else vis[x]=++cnt, c[vis[x]].push_back(i), sum1[vis[x]]+=w[i], sum2[vis[x]]+=b[i];
}
memset(f,0xcf,sizeof(f));/*所有的状态都是不合法的状态*/
f[0]=0;
for(int i=1;i<=cnt;++i)
for(int j=W;j>=0;--j)
{
if(j>=sum1[i]) f[j]=max(f[j], f[j-sum1[i]]+sum2[i]);/*需要考虑不合法的状态表示选择一整个区间*/
for(int k=0;k<c[i].size();++k)
{
if(c[i].size()==1) break;/*如果是只有一个人的话,那么他在上面的时候就已经被更新过了,所以这一步就可以被省略。*/
if(j>=w[c[i][k]])
f[j]=max(f[j], f[j-w[c[i][k]]]+b[c[i][k]]);
}
}
int ans=0;
for(int j=0;j<=W;++j)
ans=max(ans, f[j]);
printf("%d\n",ans);
return 0;
}
注意事项:注意在整组转移的时候要考虑当前的容积是否可以容纳下整个组,否则的话他就会继承下去,影响后面的选择。
可以思考一个小问题会不会有可能f[i]被一整个组以及该组中的一个人同时更新?
可以被证明:在组员人数超过一个的情况下是不可能会出现这种问题的。
因为当组员人数超过1个人时, 整个组和组中每一个成员的终点是一样的,但是因为他们的价值各不相同,所以他们的起点必然不一样(这里区分的是整个组和成员,而不是区别于成员和成员),所以就不需要考虑是否会有重复遍历的情况。