网站主要参照洛谷 牛客 力扣 cf等
题单列表 - 洛谷
题单链接
今天的刷题内容如上
在进入专题学习之前
先看补充知识
数组能开多大
C/C++数组的大小最大能有多大?_c++数组大小_JoannaJuanCV的博客-CSDN博客
全局:int 2000*2000 6个0可
局部: int 5个0可 char 6个0可
需要申请较大的空间->动态申请(new、malloc)
快读
快读/快写
快读/快写:只能处理整数读入/输出,但是要比标准输入输出函数都快得多。
- 一般来讲,快读快写在针对数据量不是很大的输入输出的时候显得比较无力,但如果是多组数据或者输入量较多,就可以显著提升效率。
- 开不开inline差不多。
- 还有一种更快的fread()函数型快读,比一般的快读都要快,但是模板不太好理解。
inline int read(){
int x=0,f=1;char c=getchar();
//这里isdigit()也可以改为通过ASCII判断,isdigit=c>='0'&&c<='9'
while(!isdigit(c))//#include<ctype>
{if(c=='-')f=-1;
c=getchar();}
while(isdigit(c)){
x=x*10+c-'0';
c=getchar();}
return x*f;
}
不过快读也有可能被卡,比如一个数据有很多很多空格
还没有见过如此毒瘤的数据
而实际上,甚至还有更快的读入方式:fread
这个函数的原理就是先把数 据流中的一整段都存下来,然后从这个数组里读取,直到数组读空了再重新从数据流中读取,由于是整段整段读取,所以自然比getchar()要快的多。
这两个东西在各种OJ上面还是适用的,因为OJ上就是文件读入输出的。
不过我们会发现,一旦用了这个读入优化。getchar,scanf都不能用了(存到buf里了),所有读入都必须自己写了。所以说数据流不是太大的时候(如1*10^6),可以考虑不用这个读入优化。
代码长这样↓
static char buf[1000000],*p1=buf,*p2=buf,obuf[1000000],*p3=obuf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-'0';c=getchar();}
return x*f;
}
scanf:
1.使用scanf的时候最容易犯的也是最常见的问题就是没打取地址符,这个问题是不会CE,只会RE,但是一定要注意!scanf输入字符串本来也没有取地址符!
2.少一个或者多一个%d也是很常见的问题,多一个%d会因为找不到地址RE,少一个%d你的变量就会少值
3.文本替代符写错类型也是一个常见问题,尤其是%lld和%d,一定要确定好读入类型再写文本替代符
读入优化:
1.读入优化最最最最最容易出现的问题就是类型,虽然只能是int或者ll,但是一定要看好读入的的变量将会是什么类型,这里千万不能写错,ll写成int你题就炸了,int写成ll可能连赋值都做不到
2.只能用于整数,如果有什么浮点数建议直接放弃
3.写的时候一定要注意好别写错了,比如少个getchar之类的,如果没了会直接TLE或者RE
fread:(建议了解下即可)
1.同读入优化
2.只能使用文件读入
快写
内容基本与快读一致,此处将给出代码但不予赘述,请读者自行理解
快速读入唯一的问题是不能输出0,这里需要自行特判
以及fwrite可以在终端输入的情况下使用,但是会直接输出为文件形式
原初の快速输出↓
inline int read()
{ int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
x=x*10+ch-'0',ch=getchar();
return x*f;
}
fwrite快速输出↓
static char buf[1000000],*p1=buf,*p2=buf,obuf[1000000],*p3=obuf;
#define putchar(x) (p3-obuf<1000000)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
inline void write(int x){
if(x==0)
{putchar('0');
return;}
int len=0,k1=x,c[10005];
if(k1<0)
k1=-k1,putchar('-');
while(k1)
c[len++]=k1%10+'0',k1/=10;
while(len--)
putchar(c[len]);
}
O2优化
我们不用管O2优化的原理,只需要记住这个优化能使得程序的效率大大提高
手动打开O2开关:
#pragma GCC optimize(2)
模拟
【算法1-1】模拟与高精度 - 题单 - 洛谷
[NOIP2003 普及组] 乒乓球
P1042 [NOIP2003 普及组] 乒乓球_哔哩哔哩_bilibili
#include<iostream>
using namespace std;
char a[2501*25+10];//每行至多25个字母,最多有2500行。
void work(int n)
{
int w=0,l=0;
for(int i=0;a[i]!='E';i++)
{
if(a[i]=='W') w++;
else l++;
if((w>=n&&w-l>=2)||(l>=n&&l-w>=2))//不要忽略题目中的每个信息
{
cout<<w<<":"<<l<<endl;
w=l=0;
}
}
cout<<w<<":"<<l<<endl;//不需要判断w!=0||l!=0
}
int main()
{
int i=0;
while(cin>>a[i]&&a[i]!='E')
{
i++;
}
work(11);
cout<<endl;
work(21);
return 0;
}
P1067 [NOIP2009 普及组] 多项式输出
同判断三角形类型
高精度
加法
#include<bits/stdc++.h>
using namespace std;
string s1,s2;
int a[1000],b[1000];
int c[1000]={0};
void jiafa()
{
int l1=s1.length();
int l2=s2.length();
for (int i=0;i<l1;i++)
a[i]=s1[i]-'0';
for(int i=0;i<l2;i++)
b[i]=s2[i]-'0';
int l=max(l1,l2);
for(int i=0;i<l;i++)
{
c[i]+=a[i]+b[i];
//进位
if(c[i]>=10)
{
c[i+1]=c[i]/10;
c[i]%=10;
}
}
if(c[l]!=0) l++;
for(int i=l-1;i>=0;i--)
cout<<c[i];
}
void fanzhuan(string s)
{
int l=s1.length();
for(int i=0,j=l-1;i<j;i++,j--)
{
swap(s[i],s[j]);
}
}
int main()
{
cin>>s1>>s2;//空格 tab 换行都可下一个
//cin.getline(s1);读一行
//gets()太危险被禁用
//fanzhuan(s1);
reverse(s1.begin(),s1.end());
reverse(s2.begin(),s2.end());
jiafa();
cout<<endl;
}
题目:P1601 A+B Problem(高精)
减法
#include<bits/stdc++.h>
using namespace std;
string s1,s2;
int a[1000],b[1000];
int d[1000]={0};
void jian(string s1,string s2)
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(d,0,sizeof(d));
//八位一复制
int l1=s1.length();
int l2=s2.length();
for(int i=0;i<l1;i++)
a[i]=s1[i]-'0';
for(int i=0;i<l2;i++)
b[i]=s2[i]-'0';
int l=max(l1,l2);
for(int i=0;i<l;i++)
{
d[i]+=a[i]-b[i];
//借位问题
if(d[i]<0)
{
d[i]+=10;//借位
d[i+1]-=1;
}
}
while(d[l-1]==0&&l>=1)
l--;//借位借没了
int flag=0;
for(int i=l-1;i>=0;i--)
{cout<<d[i];
flag=1;
}
if(flag==0)
cout<<"0";
}
int judge(string s1,string s2)
{
int l1=s1.length();
int l2=s2.length();
if(l1>l2) return 1;//长的数大
if(l1<l2) return 0;
for(int i=l1-1;i>=0;i--)
{
if(s1[i]>s2[i]) return 1;//一位一位比较 大的大
if(s1[i]<s2[i]) return 0;
}
return 1;
}
void fanzhuan(string s)
{
int l=s1.length();
for(int i=0,j=l-1;i<j;i++,j--)
{
swap(s[i],s[j]);
}
}
int main()
{
cin>>s1>>s2;//空格 tab 换行都可下一个
//cin.getline(s1);读一行
//gets()太危险被禁用
reverse(s1.begin(),s1.end());
reverse(s2.begin(),s2.end());
if(judge(s1,s2)==1) jian(s1,s2);
else{
cout<<"-";
jian(s2,s1);
}
}
乘法
#include<bits/stdc++.h>
using namespace std;
string s1,s2;
int a[1000],b[1000];
int c[1000]={0};
int cheng(string s1,string s2)
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
int l1=s1.length();
int l2=s2.length();
for(int i=0;i<l1;i++)
a[i]=s1[i]-'0';
for(int i=0;i<l2;i++)
b[i]=s2[i]-'0';
int l=l1+l2-1;/
for(int i=0;i<l1;i++)
for(int j=0;j<l2;j++)
{
c[i+j]+=a[i]*b[j];+
}
for(int i=0;i<l;i++)
{
if(c[i]>=10)
{
c[i+1]+=c[i]/10;//+
c[i]%=10;
}
}
while(c[l]>0)//这里l+1或者l都可以ac
{
l++;
if(c[l]>=10)
{
c[l+1]+=c[l]/10;+
c[l]%=10;
}
}
for(int i=l-1;i>=0;i--)
cout<<c[i];
}
int main()
{
cin>>s1>>s2;
reverse(s1.begin(),s1.end());
reverse(s2.begin(),s2.end());
cheng(s1,s2);
}
看着string里的reverse如此好用
我觉得去学一下STL中的string
string
于是去看了一篇博客
String用法详解_manonghouyiming的博客-CSDN博客
这里选取一部分内容放上
初始化
1) string s; // 生成一个空字符串s
2) string s(str) ; // 拷贝构造函数生成str的复制品
3) string s(str, stridx); // 将字符串str内"始于位置stridx"的部分当作字符串的初值
4) string s(str, stridx, strlen) ; // 将字符串str内"始于stridx且长度顶多strlen"的部分作为字符串的初值
5) string s(cstr) ; // 将C字符串(以NULL结束)作为s的初值
6) string s(chars, chars_len) ; // 将C字符串前chars_len个字符作为字符串s的初值。
7) string s(num, ‘c’) ; // 生成一个字符串,包含num个c字符
8) string s(“value”); string s=“value”; // 将s初始化为一个字符串字面值副本
9) string s(begin, end); // 以区间begin/end(不包含end)内的字符作为字符串s的初值
10) s.~string(); //销毁所有字符,释放内存
string对象的操作
string s;
1) s.empty(); // s为空串 返回true
2) s.size(); // 返回s中字符个数 类型应为:string::size_type
3) s[n]; // 从0开始相当于下标访问
4) s1+s2; // 把s1和s2连接成新串 返回新串
5) s1=s2; // 把s1替换为s2的副本
6) v1==v2; // 比较,相等返回true
7) `!=, <, <=, >, >=` 惯有操作 任何一个大写字母都小于任意的小写字母
当进行string对象和字符串字面值混合连接操作时,+操作符的左右操作数必须至少有一个是string类型的:
string s1(“hello”);
string s3=s1+”world”; //合法操作
string s4=”hello”+”world”; //非法操作:两个字符串字面值相加
4、字符串操作函数
1、string类函数
1) =, s.assign() // 赋以新值
2) swap() // 交换两个字符串的内容
3) +=, s.append(), s.push_back() // 在尾部添加字符
4) s.insert() // 插入字符
5) s.erase() // 删除字符
6) s.clear() // 删除全部字符
7) s.replace() // 替换字符
8) + // 串联字符串
9) ==,!=,<,<=,>,>=,compare() // 比较字符串
10) size(),length() // 返回字符数量
11) max_size() // 返回字符的可能最大个数
12) s.empty() // 判断字符串是否为空
13) s.capacity() // 返回重新分配之前的字符容量
14) reserve() // 保留一定量内存以容纳一定数量的字符
15) [ ], at() // 存取单一字符
16) >>,getline() // 从stream读取某值
17) << // 将谋值写入stream
18) copy() // 将某值赋值为一个C_string
19) c_str() // 返回一个指向正规C字符串(C_string)的指针 内容与本string串相同 有’\0’
20) data() // 将内容以字符数组形式返回 无’\0’
21) s.substr() // 返回某个子字符串
22) begin() end() // 提供类似STL的迭代器支持
23) rbegin() rend() // 逆向迭代器
24) get_allocator() // 返回配置器
2、函数说明
1、s.assign();
s.assign(str); // 不说
s.assign(str,1,3); // 如果str是"iamangel" 就是把"ama"赋给字符串
s.assign(str,2,string::npos); // 把字符串str从索引值2开始到结尾赋给s
s.assign("gaint"); // 不说
s.assign("nico",5); // 把’n’ ‘I’ ‘c’ ‘o’ ‘\0’赋给字符串
s.assign(5,'x'); // 把五个x赋给字符串
排序
冒泡排序、选择排序、快速排序
//快速排序
#include<iostream>
using namespace std;
int partition(int arr[], int low, int high) {
int num = arr[low];
while(low < high) {
while(low < high && arr[high] > num) {
high--;
}
arr[low] = arr[high];
while(low < high && arr[low] <=num) {
low++;//此处是<=num 并且 while不要忘记low<high
}
arr[high] = arr[low];
}
arr[low] = num;
return low;//返回low
}
void QuickSort(int arr[],int L,int R)
{
if(L<R){
int mid=partition(arr,L,R);
QuickSort(arr,L,mid-1);
QuickSort(arr,mid+1,R);
}
}
void BubbleSort(int arr[],int n)
{
for(int i=0;i<n-1;i++)
{
for(int j=i+1;j<n;j++)
{
if(arr[i]>arr[j])
{
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
}
void ChooseSort(int arr[],int n)
{
int minnum;
for(int i=0;i<n-1;i++)
{
minnum=i;
for(int j=i+1;j<n;j++)
{
if(arr[minnum]>arr[j])
minnum=j;
}
int temp=arr[i];
arr[i]=arr[minnum];
arr[minnum]=temp;
}
}
void travel(int arr[],int n)
{
for(int i=0;i<n;i++)
cout<<arr[i]<<" ";
cout<<endl;
}
int main()
{
int x;
int a[100],b[100],c[100];
int n=0;
cin>>x;
while(x!=0)
{
a[n]=x;
b[n]=x;
c[n]=x;
cin>>x;
n++;
}
// travel(a,n);
BubbleSort(a,n);
ChooseSort(b,n);
QuickSort(c,0,n-1);
travel(a,n);
travel(b,n);
travel(c,n);
}
插入排序(O(n^2))
void DirecInsSort(int* a, int n)//直接插入排序函数
{
int i, j,temp;
for (i = 1; i < n; i++) {//第一个数默认排序好了,而他的下标是0,所以从1开始
if (a[i] < a[i - 1])//如果大于就直接加入,就是相当于排好了
{
temp = a[i];
for (j = i - 1; j >= 0 && temp < a[j]; j--)//从已经排好的序列,从后往前检查
{
a[j + 1] = a[j];//往后移一个位置
}
a[j + 1] = temp;
}
}
}
希尔排序
注意:数组最后一个数一定要为1
//希尔排序
//8 3 6 1 68 12 19 3 1 0
#include<iostream>
using namespace std;
void Shell(int a[],int n,int incr)
{
int j;
for(int i=0;i+incr<n;i++)//i++哦
{
int x=a[i+incr];
for( j=i;j>=0;j-=incr)
{
if(x<a[j]) //18<10
{
a[j+incr]=a[j];
}
else break;
}
a[j+incr]=x;
}
}
void ShellSort(int a[],int n,int d[])
{
for(int i=0;i<3;i++)
{
Shell(a,n,d[i]);
for(int j=0;j<n;j++)
cout<<a[j]<<" ";
cout<<endl;
}
}
int main()
{
int a[100],n,i=0;
while(cin>>a[i]&&a[i])//输入数据
i++;
n=i;
int d[10];//incr
cin>>d[0]>>d[1]>>d[2];
ShellSort(a,n,d);
}
void Shell(int a[],int n,int incr)
{
int j;
for(int i=incr;i<n-incr;i++)
{
if(a[i]<a[i-incr])
{
int temp=a[i];
for(j=i-incr;j>=0&&a[j]>temp;j-=incr)
{
a[j+incr]=a[j];
}
a[j+incr]=temp;
}
}
}
归并排序
#include<bits/stdc++.h>
using namespace std;
void Merge(int Arr[],int start,int mid,int endd)
{
int temp[10010];
int i=start,j=mid+1,k=start;
while(i!=mid+1&&j!=endd+1)
{
if(Arr[i]>Arr[j])
temp[k++]=Arr[j++];
else
temp[k++]=Arr[i++];
}
while(i!=mid+1)
temp[k++]=Arr[i++];
while(j!=endd+1)
temp[k++]=Arr[j++];
for(i=start;i<=endd;i++)
Arr[i]=temp[i];
}
void MergeSort(int a[],int start,int endd)
{
int mid;
if(start<endd)
{ mid=start+(endd-start)/2;
MergeSort(a,start,mid);
MergeSort(a,mid+1,endd);//左右两块
Merge(a,start,mid,endd); }
}
int main()
{
int n;
int a[10010];
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
MergeSort(a,0,n-1);
for(int i=0;i<n;i++) cout<<a[i]<<" ";
return 0;
}
第二种写法(一个函数):
void msort(int b,int e)//归并排序
{
if(b==e)
return;
int mid=(b+e)/2,i=b,j=mid+1,k=b;
msort(b,mid),msort(mid+1,e);
while(i<=mid&&j<=e)
if(a[i]<=a[j])
c[k++]=a[i++];
else
c[k++]=a[j++],ans+=mid-i+1;//统计答案
while(i<=mid)
c[k++]=a[i++];
while(j<=e)
c[k++]=a[j++];
for(int l=b;l<=e;l++)
a[l]=c[l];
}
堆排序
可移步这道题第二篇题解 我觉得写的非常好!!!
登录 - 洛谷
//堆排序
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
void SiftAdjust(int a[],int n,int i)
{
//n是a数组有效数据的范围,它是慢慢变小的
//i是要调整的那个节点的编号
int f,left;//f 用来记忆双亲 left算出i的左孩子编号
for(f=i,left=2*i+1;left<=n-1;)//计算新的孩子
{//f记下要调整的i号节点,keft就变成右孩子的下标
if(left+1<=n-1 && a[left]<a[left+1] )//右孩子存在,且,右边值更大
left=left+1;//left++ left就变成右孩子的下标
if(a[left]>a[f])//孩子的最大值和双亲f比,若孩子更大,则交换
{
int t=a[left];
a[left]=a[f];
a[f]=t;
}
else break; //孩子的值 小,就结束 return
f=left; //继续向下调整,刚才的孩子成为新的双亲,再上去计算新的孩子
left=2*f+1; //计算出新孩子,这句话放for表达式3也可以
}
}
void HeapSort(int a[],int n)
{
//8 3 6 1 68 12 19 3 1
//k=0 1 2 3 4 5 6 7 8
//调整 中间节点
for(int i=(n-2)/2;i>=0;i--)
{
SiftAdjust(a,n,i);
}
//调整根节点
for(int j=0;j<n;j++)
cout<<a[j]<<" ";
cout<<endl;
for(int i=0;i<n-1;i++)
{
int t=a[n-1-i];// n-1 n-2 n-3 n-4
a[n-1-i]=a[0];
a[0]=t;//交换第一个与最后一个数
SiftAdjust(a,n-1-i,0);//调整剩下n-1-i个 维护根
for(int j=0;j<n;j++)
cout<<a[j]<<" ";
cout<<endl;
}
}
int main()
{
int a[100],i=0;
while(cin>>a[i]&&a[i])
{
i++;
}
int n=i;
HeapSort(a,n);
return 0;
}
维护堆排序
void heapify(int arr[],int n,int i)
{
int largest=i;
int lson=i*2+1;
int rson=i*2+2;
if(lson<n && arr[largest]<arr[lson])
largest=lson;
if(rson<n&&arr[largest]<arr[rson])
largest=rson;
//找出三个节点中最大的节点
if(largest!=i)
{
swap(&arr[largest],&arr[i]);
heapify(arr,n,largest);
}
}
桶排序
桶排序是计数排序的升级版,也是分治算法。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。简言之,将值为i的元素放入i号桶,最后依次把桶里的元素倒出来。
具体移步:https://blog.csdn.net/m0_64036070/article/details/123826962
#include<bits/stdc++.h>
using namespace std;
int a,n,m,b[1000];
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++)cin>>a,++b[a]; //记录票出现的次数
for(int i=0;i<1000;i++)while(b[i]--)cout<<i<<" "; //根据票出现的次数输出
return 0;
}
总结:
比赛:小数据用插入;大数据用快排
P1923 【深基9.例4】求第 k 小的数
思路 1 (60pts): O(nlog2n)
想法:对原数组进行快速排序,然后 O(1) 输出。
#include<bits/stdc++.h>
using namespace std;
int x[5000005],k;
int main()
{
int n;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
scanf("%d",&x[i]);
sort(x,x+n);//快排
printf("%d",x[k]);
}
思路 2 (100pts): O(n)
想法:根据快排思想来寻找第 k 小的数。
快排的核心思想是二分。
在原二分的基础上可以进行修改:因为每次递归都会将数组划分为三部分,而答案只会在这三部分中的一个,不需要再对其他部分进行搜索,从而达到优化时间复杂度的效果。
#include<bits/stdc++.h>
using namespace std;
int x[5000005],k;
void qsort(int l,int r)
{
int i=l,j=r,mid=x[(l+r)/2];
do
{
while(x[j]>mid)
j--;
while(x[i]<mid)
i++;
if(i<=j)
{
swap(x[i],x[j]);
i++;
j--;
}
}
while(i<=j);
//快排后数组被划分为三块: l<=j<=i<=r
if(k<=j) qsort(l,j);//在左区间只需要搜左区间
else if(i<=k) qsort(i,r);//在右区间只需要搜右区间
else //如果在中间区间直接输出
{
printf("%d",x[j+1]);
exit(0);
}
}
int main()
{
int n;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
scanf("%d",&x[i]);
qsort(0,n-1);
}
思路 3 (100pts):O(n)
C++ 的宝库:STL
在 STL 里有一个神奇的函数 nth_element
。
它的用法是 nth_element(a+x,a+x+y,a+x+len);
。
结论:差不多就是将我们的思路 2 做了一遍。
nth_element
的时间复杂度是O(n) 的,不过 STL 常数普遍较大……但还是能过此题。
#include<bits/stdc++.h>
using namespace std;
int x[5000005],k;
int main()
{
int n;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
scanf("%d",&x[i]);
nth_element(x,x+k,x+n);//简短又高效
printf("%d",x[k]);
}
在 Ta 的博客查看
刚翻了一下原来P1138第k小整数,发现竟然没人用nth_element神器,这里介绍一下。
在强大的STL库中存在一个神奇的函数,那就是nth_element,这个函数主要用来将数组元素中第k小的整数排出来并在数组中就位,随时调用,可谓十分实用。
函数语句:nth_element(数组名,数组名+第k小元素,数组名+元素个数)
具体如何用见下面AC代码:
#include<bits/stdc++.h>
using namespace std;
long long n,k,a[5000010];
int main()
{
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
nth_element(a,a+k,a+n);//使第k小整数就位
printf("%d",a[k]);//调用第k小整数
}
暴力枚举
加油