1.vector
- 头文件
- 定义:
- vector name;
- vector a[asize]; //外层不变内层可变
- vector<vector> a; //两层都可变,用的比较少
- 访问:
- 下标访问:name[1];
- 迭代器访问: vector::iterator it;
- 可以用 *(it+3) 来访问
- 可以 it++;
- 实例应用:
- begin(),end():左闭右开
- push_back(3);放到末尾用的
- pop_back();删除尾元素
- size();返回的是个数
- clear();清空
- insert(it,x);在 it 指向的地方插入x
- erase
- erase(it); 删除迭代器it处的元素
- erase(it1,it2);删除左闭右开区间的所有元素
- 注意:
- 不支持it<vi.end();这样的写法,只能写it!=vi.end();
- 遍历语句:
- for(vector::iterator it =vi.begin();it!=vi.end();it++);
- for(int i=0;i<vi.size();i++);
- 不超过109的数都可以用int;
- 不超过106的数组,可以在main内部申请,否则放到外面去
- 应用:
为了方便后面使用,先补充一个哈希
哈希补充:
找一组数据在另一组数据中是否出现过时,可以采用此法
- 数1中找数2出现过吗:先设置一个哈希表(数组),初始化使得所有的小格格都比正常值小,把数1直接对应到下标,然后用该小格格对应出现次数,然后直接遍历数2查找对应下标出现的次数。
- 剩下的:
当数很大时,或者要用字符串对应到下标时,直接法就失效了,所以引入了哈希函数解决对应问题- 模运算法:用哈希表表长作模(为避免重复尽量选的是质数),对大数进行模运算,所得的数就不太有重复的,然后,如果重复的少,就线性探查,重复的多就正向平方探查
- 链地址法:用一个头结点数组来放置,当有冲突的就链在后面,之后再从里面筛选就行
字符串哈希初步
也就是上面提到的用字符串对应下标
若只有大写字母,则采用26进制,若加上了小写字母,则采用52进制,若再加上了数字,可以采用62进制,但假如数字再最后,还可以直接采用52进制转换完成后,把数字跟上即可,然后小格格里面放上次数即可(别忘了先初始化,要不放不进去)。
补充: R进制转十,进制,假设最高项系数是a1之后一直到n,则从1开始循环n-1(因为最高项是R的n-1次幂)次,循环语句为: y=y*R+a;百试百灵,密不外传,哈哈。
- A1039
题意:N个学生,K门课。给出学生姓名,然后查它报了那些课。
思路:N比较大, 直接用一个二维数组(第一维放姓名第二维放科目),恐怕会溢出,因为有四万个学生,两千五百门课,乘积太大,所以采用vector进行存放,但是就要设置一个对应关系来对应了,当然想到了map函数进行对应,但是假如四十万学生一起查询的时候,就要对应四十万次,并且map还不能用字符数组,只能用string,这时间肯定超出了,所以不要用,还是用哈希函数来进行对应,对应的时候,就是用之前字符串对应的方法。所以就引申出来这个题的解题思路,两层循环,外层循环课程数,内层循环姓名数,来一个名字对应一遍把vector中的对应过去的地方后面添上课程数。然后开始输出即可。
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define M 300000//姓名哈希过去的上界
vector<int> an[M];//每个学生选择的课程编号
int getID(char* name){//用其来找每个人的ID号
int y=0;
for(int i=0;i<3;i++){//先把前三个字母转化过去,
y=y*26+(name[i]-'A');
}
y=y*10+(name[3]-'0');
return y;
}
char name[10];
int n,k,a,b,c,d;//n是人数,k是课程数,a是课程号,b是选课人数,c是名字对应过来的数,d用来放该学生选课数
int main(){
while(scanf("%d%d",&n,&k)!=EOF){
for(int i=0;i<k;i++){
scanf("%d%d",&a,&b);
for(int j=0;j<b;j++){
scanf("%s",name);
c=getID(name);
an[c].push_back(a);
}
}
for(int i=0;i<n;i++){
scanf("%s",name);
c=getID(name);
printf("%s %d",name,an[c].size());
sort(an[c].begin(),an[c].end());
for(int j=0;j<an[c].size();j++){
printf(" %d",an[c][j]);
}
printf("\n");
}
}
return 0;
}
- A1047
题意:N个学生,K个课,给了每个人的名字和选的课,然后,按照顺序输出每门课的学生,课程按课程号,学生按照字母序
思路:与上一个题差不多的思路,上一个题是,给一个数组,每个格子是一个vector,下标是学号(用哈希函数),格子里面是课程号,然后放完了以后再排序输出,本来他就是一个二维的对应关系,但是只是直接开二维数组太大了,而且map不好用才用的哈希,这个题也差不多,这个题是第一维是课程号,第二维是学生,最后按顺序输出。第一维的课程号用数组没有问题,第二维的学生也是多少不一,所以不太可以用二维数组,但是可以用vector,解决第二维,但是第二维有一个问题就是第二维的排序不好拍,用set又太慢了,因为那个是一个个排得,用哈希的话,只能是那字符串对应数,我输出的时候,手里的是数,是没法用反函数输出的, 所以说,只能是采用一个哈希表,把字符串放进去,让其对应到一个数字上,然后把这个数字排序,然后再通过这个数字我还能返回回去,这也就确定了哈希函数的方法,也就是必须生成的是按字典序的数字,那么如果采用二十六进制的话,发现是可以的所以可以做了,但是,这样的话虽然是可以的,但不是最好的,可以先把他随意对应到一个表里面,用sort函数里面的cmp函数,比较里面的字符串即可,然后调整下标就可以。
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> course_stu[3000];
char name[40010][5];
int cmp(int a,int b){
return strcmp(name[a],name[b])<0;//按字典顺序排列
}
int main(){
int n,k;
scanf("%d%d",&n,&k);//n是学生数,k是课程数
for(int i=0;i<n;i++){//输入的时候是先输入的学生,内层才是课程,只是存储的时候需要倒过来
int ncrs,crs;//ncrs表示所选课程总数,crs表示所选课程的编号
scanf("%s%d",name[i],&ncrs);
for(int j=0;j<ncrs;j++){
scanf("%d",&crs);
course_stu[crs].push_back(i);
}
}
for(int i=1;i<=k;i++){
printf("%d %d\n",i,course_stu[i].size());
sort(course_stu[i].begin(),course_stu[i].end(),cmp);
for(int j=0;j<course_stu[i].size();j++){
printf("%s\n",name[course_stu[i][j]]);
}
}
return 0;
}
2.set
- 概括:内部自动有序,不含有重复元素 #include
- 定义:set name;
- 访问:只能用迭代器访问,且不支持加数字的方式
- 常用实例:
- insert();自动插到改在的地方
- find(value);返回它该在的地方的迭代器
- ease()
- 删除迭代器指向的
- 直接删除一个元素
- 删除一个左闭右开区间
- size();
- clear();
A1063
题意:算重复率,分子是交集,分母是并集
思路:这个题的思路就是求一个交集,一个并集,首先用set存每个集合,然后,交集的话,可以用哈希表来做,并集的话,也可以用哈希表来做,但是哈希表对应是个问题,10^9太大了,不能直接对应,但是,小了估计也不行,所以这里要另谋出路,因为set有一个函数可以进行查找值,所以问题解决了。
补充: printf的输出格式
%5.2f,两位小数,前面5位
%5d,前面五位的整数
%-d,左对齐,否则为右对齐
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
set<int> st[60];
int main(){
int n,mem,ele;//n表示的是有几个集合,mem表示里面有几个元素,ele表示的是集合的元素
scanf("%d",&n);
for(int i=1;i<=n;i++){//此处的i表示第几个集合
scanf("%d",&mem);
for(int j=0;j<mem;j++){
scanf("%d",&ele);
st[i].insert(ele);
}
}
int que,j,b,q,u;//que表示几组查询,j表示交集元素个数,b表示并集元素个数,q和u表示比较的两组
scanf("%d",&que);
for(int i=0;i<que;i++){//i负责查询循环
scanf("%d%d",&q,&u);
j=0;b=st[q].size();
for(set<int>::iterator it=st[u].begin();it!=st[u].end();it++){//开始比较了,以u找q
if(st[q].find(*it)!=st[q].end()){//不在最后出现,说明找到了
j++;
}
else b++;//没找到就只加并集
}
printf("%.1f%\n",j*100.0/b);
}
return 0;
}
3.string
- 概况:string,
- 定义:string str;,并且还可以直接初始化string str=“abcd”;
- 访问:下标访问: 注意,输入输出只能用cin,cout,输出也可以转化为,c_str(),转化为字符数组,用printf输出。迭代器访问: 可以加数字了
- 实例:
- +=,后跟到前
- !=,<…这一些,可以直接按照字母序比较
- length()/size(),两个功能一样,不算\0的长度
- insert();
- insert(pos,string);在该位置加入字符串
- insert(it,it2,it3),在it位置插入左开由闭区间的2到3
- earse():单个元素和区间,注意区间可以从某个位置开始的几个
- earse(it)
- earse(pos,length),这里是pos号位,不是滴第pos个
- earse(first,last)
- clear()
- substr(pos,len),pos号位开始的len长子串
- string::npos,查找成功就不是这个数,查找不成功就是这个数
- find()
- find(str2)
- find(pos,str2),从str1的pos位,开始找str2
- replace(pos,len,str2)/replace(it1,it2,str2)
A1060
题意:科学记数法的转换
思路:数有两种,0.几几几的那种,和几几几.几几几的那种,有一个小陷阱,就是可能有前导0,要先去掉,然后,剩下的,就是找指数e和前面几个数字了,指数的e:0.几几几的这种,从.开始往后数,一直到数字,几几几的这种就是从头数到点。 前面的数字:0.几几几的这种先去掉前面的0,然后取数,几几几点的这种,先去掉小数点,然后取数。观察发现,其都跟小数点有关,也就是说,要么都得到小数点,要么都从小数点开始,所以先去掉小数点,去小数点的过程中数指数。然后这个都是对于一串字符的处理所以用string。
注意:
- string的遍历不要用迭代器,因为不好停下来,用 i 直接遍历就行
- 字符的0和数字的0不一样,所以用字符的0时一定要加
‘’
- string的find函数找的是字符串,返回的是位置(也就是下标),不是迭代器,如果没找到,返回的是string::npos
- substr的参数是位置,不是迭代器,所有pos+len的都是位置,不是迭代器
- 位置和迭代器的转换 str.begin()+pos=it,其实也好记,因为只有string和vector可以加数字,所以也就只有这两个会出现位置的问题,所以,其他的用的和返回的都是迭代器,只用特殊记一下这两个的就可以,观察发现,vector中没有yongpos的,而string中只要有参数的都可以用pos,所以记住了,再加上一个find返回的是pos,所以就记住了
#include<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
void DealS(string &str,int dig,int &e,string &num){//去掉前导0,去除小数点,找e,找num
while(str.length()>0&&str[0]=='0'){//去掉前导0
str.erase(str.begin());
}
if(str.length()==0)e=0;
else if(*(str.begin())=='.'){//表示0.几几几的类型
str.erase(str.begin());//先去掉小数点
while(str.length()>0&&str[0]=='0'){
e--;
str.erase(str.begin());
}
if(str.length()==0)e=0;
}
else{//表示几几几.几几几的类型
int k;
for(k=0;k<str.length()&&str[k]!='.';k++){
e++;
}
if(k<str.length()){
str.erase(str.begin()+str.find('.'));
}
}
while(str.length()<dig){
str+='0';
}
num=str.substr(0,dig);
}
int main(){
string str1,str2;
int dig,e1,e2;//dig表示有效数字的位数,e表示指数,num表示取到的数字
e1=e2=0;
string num1,num2;//num表示取到的数字
while(scanf("%d",&dig)!=EOF){
cin>>str1>>str2;
DealS(str1,dig,e1,num1);
DealS(str2,dig,e2,num2);
if(e1==e2&&num1==num2){//判断相等并输出
cout<<"YES "<<"0."<<num1<<"*10^"<<e1<<endl;
}
else{
cout<<"NO "<<"0."<<num1<<"*10^"<<e1<<" 0."<<num2<<"*10^"<<e2<<endl;
}
}
return 0;
}
4.map
- 概况:
- 定义: map<typename1,typename2> mp; 注意不可以使用字符数组对应,只能用string
- 访问:下标访问 mp[key] ,迭代器访问 it->first 是key it->second 是值
- 实例:
- find(key) ,返回的是迭代器
- erase()
- erase(it)
- erase(key)
- erase(first,lasr)
- size(),返回的是对数
- clear()
- 用途:
- 判断大整数是否存在
- 字符串和整数之间的映射
A1100
题意:给了火星文和地球文的转换方法,给一个数并进行转化
思路:因为数不是很多,所以直接把所有的都整出来再输出就行了,但是必须是双向的,也就是说,输入字符串得对应数字,输入数字得对应字符串,但是,如果这里很多的话,就用哈希函数了,但是很少数据,所以直接用map解决字符串对应数字的问题即可。然后就是数制转换的问题了,数制转换,正常的转化方式是R进制高位开始乘R,加起来,但是这种方法不是很好因为有一些数总是重复计算的,比如单个位和单十位总是重复计算,所以把他单拿出来先算就好了,然后组合一下就可以。
注意:
- 不能引用的,就把他整成全局变量即可
- 如果结果出现问题,就一个个的实验就行
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<string>
#include<map>
using namespace std;
map<string,int> trans;
string res[13*13];
void creat(string g[20],string s[20]){//个位用g,十位用s
trans["tret"]=0;res[0]="tret";
for(int i=0;i<12;i++){//先算所有单个位的
trans[g[i]]=i+1;
res[i+1]=g[i];
}
for(int i=0;i<12;i++){//再算十位的
trans[s[i]]=13*(i+1);
res[13*(i+1)]=s[i];
}
for(int i=0;i<12;i++){//再算剩下的,就是两位都有的了
for(int j=0;j<12;j++){
string str=s[i]+' '+g[j];
trans[str]=trans[s[i]]+trans[g[j]];
res[trans[s[i]]+trans[g[j]]]=str;
}
}
}
int recog(string str){
if(str[0]>='0'&&str[0]<='9')return 1;
if(str[0]>='a'&&str[0]<='z')return 2;
}
int main(){
string g[20]={"jan", "feb","mar", "apr", "may", "jun", "jly", "aug", "sep", "oct", "nov", "dec"};
string s[20]={"tam", "hel", "maa", "huh", "tou", "kes", "hei", "elo", "syy", "lok", "mer", "jou"};
creat(g,s);
int n;
scanf("%d",&n);
getchar();//用来防止数后面的getline吸收掉回车
string str;
for(int i=0;i<n;i++){
getline(cin,str);
while(str.length()>4&&str.find("tret")==0)//去掉前导0
str.erase(0,5);
while(str.length()>0&&str[0]=='0'){//去掉前导0
str.erase(str.begin());
}
if(str.length()==0)
str+='0';
if(recog(str)==1){//如果输入的是一个数
int t=0;
for(int j=0;j<str.length();j++){//把字符转化为数字
t=t*10+str[j]-'0';
}
cout<<res[t]<<endl;
}
else if(recog(str)==2){//这就是个火星文
printf("%d\n",trans[str]);
}
}
return 0;
}
A1054
题意:M*N的图像,N行M个色素块,求主要的色彩(必须超过半数区域)
思路:如果二维数组暴力解的话肯定出问题,然后这一章学的又是map所以,可以尝试对应关系,目的是求出现次数最多的那个那么我觉得可以让每个色彩对应一个出现次数,bingo。
#include<cstdio>
#include<map>
#include<algorithm>
#include<string>
using namespace std;
map<int,int> num;
int main(){
int m,n,t;//m列,n行,t是每个块的值
scanf("%d%d",&m,&n);
for(int i=0;i<m*n;i++){
scanf("%d",&t);
if(num.find(t)!=num.end()){//也就说找到了
num[t]++;
}
else num[t]=1;
}
map<int,int>::iterator max=num.begin();//max,表示最大值的迭代器
for(map<int,int>::iterator it=num.begin();it!=num.end();it++){//遍历
if(max->second<it->second){//更新max
max=it;
}
}
printf("%d\n",max->first);
return 0;
}
A1071
题意:每个人输入一句话,然后找出现最多的单词
思路:先得写一个转化函数,把大写的转化为小写的,然后还得写一个判断是否为字符函数,然后从头至尾截断判断,输出即可,但是遇到了个问题,就是string干啥啥还不方便但是对应的时候,还得用他,所以只用他对应,剩下的字符数组来干。但是发现字符数组好是好,但是不能随意变换长度,所以很可能就失败了,所以还是要用string来存放只不过内外两层循环就行了,外层循环处理句子,内层循环处理单词。
心得:
- 每次用完string,必须清空。
- 如果循环内的循环可能会触及外循环边界,那么内循环也要加上跳出条件,并且还要加在前面,加在后面很有可能就失败了
string str;
getline(cin,str);//读入string
char str2[1024];
cin.getline(str2,1024);//读入char数组
//注意不要用cin.get,他不会丢弃换行符,也不会输进去,会留在缓冲区,可能对后序造成影响
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<map>
#include<iostream>
using namespace std;
map<string,int> num;
int judge_trans(char &w){
if(w>='0'&&w<='9'||w>='a'&&w<='z')return 1;
else if(w>='A'&&w<='Z'){
w-='A'-'a';
return 1;
}
else return 0;
}
int main(){
string str;
getline(cin,str);
int i=0;
while(i<str.length()){
string word;
while(judge_trans(str[i])&&i<str.length()){//如果是合法字符,说明单词没结束
word+=str[i];
i++;
}
//如果不是合法字符,说明单词结束,并且要找到下一个合法字符
if(num.find(word)!=num.end())
num[word]++;
else num[word]=1;
while(i<str.length()&&!judge_trans(str[i]))
i++;
}
int max=0;//存放出现最多的单词
string ans;
for(map<string,int>::iterator it=num.begin();it!=num.end();it++){
if(max < it->second){
max=it->second;
ans=it->first;
}
}
cout<<ans<<" "<<max<<endl;
system("pause");
return 0;
}
A1022
题意:给了一本书的六条信息,一共n本书,然后,按五种查询方法输出
思路,每种信息,一个分类表,进行map,然后,查询的时候遍历就行了,关键字的提取方法和上个题的差不多,但是有一个问题,上一个题是对应出现的次数,但是这个题对应的是一组数,所以不能完全按照上个题的来,所以这里用string对应的不是string,也不是int而是set,太神奇了
注意:
- 把字符串截断成为每个单词很简单,只需要cin或者scanf的循环即可,但是这种只限于空格等的分割,若还有其他要求就失效了。
- 用int存储学号有一个问题就是如果开头是0的输出的位数就不对了,所以用%07d输出
- 判断语句要用==
- 数字后面的getline要先getchar()
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<iostream>
#include<map>
#include<set>
using namespace std;
map<string,set<int>> B[6];
int judge(char w){
if(w>='0'&&w<='9'||w>='a'&&w<='z'||w>='A'&&w<='Z')return 1;
else return 0;
}
int main(){
int n;//n本书
scanf("%d",&n);
getchar();
string str;
int num;
for(int i=0;i<n;i++){
scanf("%d",&num);
getchar();
for(int j=1;j<=5;j++){
if(j==3){//如果是3,就是关键字截取部分
string word;
char c;
while(cin>>word){
B[3][word].insert(num);
c=getchar();
if(c=='\n')break;
}
}
else {
getline(cin,str);
B[j][str].insert(num);
}
}
}
int m,on;//m次查询,on为当前查询类别
scanf("%d",&m);
getchar();
for(int i=0;i<m;i++){
getline(cin,str);
on=str[0]-'0';
cout<<str<<endl;
str.erase(0,3);
if(B[on].find(str)!=B[on].end()){//如果找到了,就遍历set中的值即可
for(set<int>::iterator it=B[on].find(str)->second.begin();it!=B[on].find(str)->second.end();it++){
printf("%07d\n",*(it));
}
}
else printf("Not Found\n");
}
system("pause");
return 0;
}
静态链表
总结:
- 不需要头结点
typedef struct Node{
typename data;//数据域
int next;//指针域
}node[size];
- 不要让结构体和结构体变量用同一个名字,否则排序会很麻烦
A1032
题意:有一些单词有重复的连续字母,所以可以一起存储,给了两个首地址,还有n个结点,每个节点先是一个5位的整数,然后是他的数据,然后是下一个结点的地址,输出首个重复元素的地址,如果没有输出-1
思路:两个字符串比较,我们原来用到过哈希表,但是这个题给了的是一堆地址,所以很明显是用链表,而且是静态链表,首先是构造静态链表,构造的时候进行标记,看看原来有没有,最后按链表遍历就完事了。
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
struct NODE{
char w;
int next;
int tag;//tag=1的时候是第一次来,tag=2的时候是2次来
}node[100000];
int main(){
int b1,b2,n;//b1是1链开始,b2是2链开始,也用来做指针
scanf("%d%d%d",&b1,&b2,&n);
int p,q;//p用来接收当前地址,t是data,q是下一个地址
char t;
for(int i=0;i<n;i++){
scanf("%d %c %d",&p,&t,&q);
node[p].w=t;
node[p].next=q;
}
for(p=b1;p!=-1;p=node[p].next){//先标记第一个链,如果全标记了就没有用了
node[p].tag=1;
}
for(p=b2;p!=-1;p=node[p].next){//遍历第二个链找第一个
if(node[p].tag==1)break;
}
if(p!=-1)printf("%05d",p);
else printf("-1\n");
system("pause");
return 0;
}