Acwing菜鸟闯关记(一)
鄙人机试能力极差,相关算法与板子记不住,借助y神Acwing平台,希望能给自己的机试能力上升几个段位。目前是算法基础课的第一讲。
快速排序
785.快速排序
对于快速排序的板子,关键在于左右双指针是如何移动的,笔者认为关键在于哨兵。
选取的哨兵,第一次快排后左边的都是比它小的,右边都是比他大的。之后再不断递归,当l>=r时,跳出递归。
void quicksort(int l,int r,int a[]){
if (l>=r)
return;
int rnd = rand()%(r-l+1)+l;
// printf("%d",a[rnd]);
swap(a[rnd],a[l]);
int x = a[l];
int i=l-1,j=r+1;
while(i<j){
do i++; while (x>a[i]);
do j--; while (x<a[j]);
if (i<j)
swap(a[i],a[j]);
}
quicksort(l,j,a);
quicksort(j+1,r,a);
}
需要注意的是,选取a[l]是有风险的。不如随机数更合适。l>=r是个界限,注意这种情况下不必再递归。
rnd = rand()%(r-l+1)
786.找第k个数
void quicksort(int a[],int l,int r){
if (l>=r)
return;
// int rnd = rand()%(r-l+1)+l;
// swap(a[l],a[rnd]);
int i = l-1,j = r+1;
int x = a[l];
while(i<j){
do i++; while (x>a[i]);
do j--; while (x<a[j]);
if (i<j)
swap(a[i],a[j]);
}
if (k-1<=j)
quicksort(a,l,j);
else
quicksort(a,j+1,r);
}
与快排相同,由于选中的是第k个数,所以用快排更加迅捷。注意区分分界点。
快速排序时间复杂度最差:O(n^2);
平均时间复杂度:O ( n l o g 2 n )
快速排序是不稳定的。
归并排序
787.归并排序
归并排序的难点在于开创tmp数组进行两个序列的比较。
需要有mid进行确定。
void merge_sort(int a[],int l,int r){
if (l>=r)
return;
int mid = (l+r) >>1;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
int k = 0,i=l,j=mid+1;
while(i<=mid&&j<=r){
if (a[i]<=a[j])
tmp[k++] = a[i++];
else tmp[k++] = a[j++];
}
while(i<=mid)
tmp[k++] = a[i++];
while(j<=r)
tmp[k++] = a[j++];
for(i = l,j=0;i<=r;i++,j++)
a[i] = tmp[j];
}
注意是先递归,之后合并。
l>=r不能忘记,同时最后赋值回原数组时应确定a数组的初始与tmp数组的初始下标。
归并排序是稳定的。
时间复杂度:O ( n l o g 2 n ) 。
788.逆序对的数量
LL merge_find(int l,int r){
if (l>=r)
return 0;
int mid = (l+r)>>1;
LL res = merge_find(l,mid) + merge_find(mid+1,r);
int i = l,j = mid+1;
int k = 0;
while(i<=mid&&j<=r){
if (a[i]<=a[j]) tmp[k++] = a[i++];
else {
tmp[k++] = a[j++];
res += mid-i+1;
}
}
while(i<=mid) tmp[k++] = a[i++];
while (j<=r) tmp[k++] = a[j++];
for (int i = l,j=0; i <=r ; ++i,++j) {
a[i] = tmp[j];
}
return res;
}
难点在于如何找出逆序对数量,按照归并排序正常进行。但到后面采用,res+=mid-i+1直接选用。因为局部有序,该数符合逆序对,后面的都是符合要求的逆序对。
二分查找
789.数的范围(整数二分)
while(q--){
int find;
scanf("%d",&find);
int l=0,r=n-1;
while(l<r){
int mid = l + r >> 1;
if(a[mid]>=find) r = mid ;
else l = mid+1;
}
if(a[l] != find) cout<<"-1 -1"<<endl;
else {
cout<<l<<" ";
r = n-1;
while(l<r){
int mid = l+r+1 >>1;
if (a[mid] <= find) l = mid;
else r = mid-1;
}
cout<<r<<endl;
}
}
整数二分的问题在于,是分割成[l,mid-1]和[mid,r]还是[l,mid]和[mid+1,r]。
C语言向下取整,选择[l,mid-1]和[mid,r]则需要mid = l+r+1>>1
选择[l,mid]和[mid+1,r],则mid= l+r>>1
790.数的三次方根(实数二分)
double l = -10000,r = 10000;
while(r-l > 1e-8) {
double mid = (l + r) / 2;
if (mid * mid * mid >= q)
r = mid;
else l = mid;
}
实数二分注意区间即可。
高精度
791.高精度加法
vector<int> add(vector<int> &A,vector<int> &B){
int t=0;
vector<int> C;
for (int i = 0; i < A.size()|| i<B.size(); ++i) {
if (i<A.size()) t+=A[i];
if (i<B.size()) t+=B[i];
C.push_back(t%10);
t/=10;
}
if (t!=0) C.push_back(t);
return C;
}
vector<int> A,B;
cin>>a;//1234
cin>>b;
for (int i = a.length()-1; i >= 0; i--) {
A.push_back(a[i]-'0');
}
for (int i = b.length()-1; i >=0; i--) {
B.push_back(b[i]-'0');
}
auto C = add(A,B);
for (int i = C.size()-1; i >=0; i--) {
cout<<C[i];
}
首先注意读进来后要进行逆置,因为先进行个位加。
之后进行高精度加,每一位加完之后形成的和要进行对10取余留在本位,和除以十进位。
注意输出的时候也应该逆置输出。
792.高精度减法
// C = A - B, 满足A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;
for (int i = 0, t = 0; i < A.size(); i ++ ) //t 用来借位
{
t = A[i] - t;
if (i < B.size()) t -= B[i]; //判断B是否有这一位
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back(); // 去掉结果前缀和前面的0
return C;
}
其他操作类同高精度加法,但应注意到减法应交换数,保证大数减小数。
793.高精度乘法
vector<int> mul(vector<int> &A,int b){
int t=0;
vector<int> C;
for (int i = 0; i < A.size()||t; ++i) {
if(i<A.size()) t+=A[i]*b;
C.push_back(t%10);
t/=10;
}
return C;
}
794.高精度除法
vector<int> div(vector<int> &A,int b,int& r){
r=0;
vector<int> C;
for (int i = A.size()-1; i >= 0; i--) {
r = r*10+A[i];
C.push_back(r/b);
r %= b;
}
reverse(C.begin(),C.end());
while(C.back()==0&&C.size()-1) C.pop_back();
return C;
}
前缀和与差分
795.前缀和
for (int i = 1; i <= n; ++i) {
cin>>a[i];
}
for (int i = 1; i <= n; ++i) {
S[i] = S[i-1]+a[i];
}
前缀和的目标就是优化,让时间复杂度从O(n)变O(1)
796.前缀和子矩阵
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m ; ++j) {
cin>>a[i][j];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m ; ++j) {
S[i][j] = S[i-1][j]+S[i][j-1]+a[i][j]-S[i-1][j-1];
}
}
while(q--){
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
cout<<S[x2][y2]+S[x1-1][y1-1]-S[x2][y1-1]-S[x1-1][y2]<<endl;
}
前缀和子矩阵的问题是注意加回重复值。
前缀和数组注意开头下标是1,否则越界。
797.差分
void insert(int l,int r,int c){
b[l] += c;
b[r+1] -= c;
}
int main(){
scanf("%d%d",&n,&m);
for (int i = 1; i <= n; ++i) {
scanf("%d",&a[i]);
}
for (int i = 1; i <= n; ++i) {
insert(i,i,a[i]);
}
while(m--){
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
insert(l,r,c);
}
for (int i = 1; i <= n; ++i) {
b[i]+=b[i-1];
}
注意insert函数,差分的本质
b1 = a1; b2 = a2 - a1 … bn = bn - bn-1
使区间[l,r]中的每一个a[i]+c,则只需b[l]+c,b[r+1]-c。
注意下标开始也是1
798.差分矩阵
与前缀和看的矩阵相反,类比差分
void insert(int x1,int x2,int y1,int y2,int c){
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%d",&a[i][j]);
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
insert(i,i,j,j,a[i][j]);
}
}
while(q--){
int x1,x2,y1,y2,c;
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
insert(x1,x2,y1,y2,c);
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];
printf("%d ",b[i][j]);
}
puts("");
}
给以(x1, y1)为左上角,
(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
双指针
799.最长连续不重复子序列
int res = 0;
for (int i = 0,j= 0; i < n; ++i) {
s[a[i]]++;
while(s[a[i]]>1){
s[a[j]]--;
j++;
}
res = max(res,i-j+1);
}
两个指针同时从开头走,i,j注意。
位运算
801.二进制中1的个数
int lowbit(int n){
return n & -n;
}
while(x){
x -= lowbit(x);
res++;
}
注意x -= lowbit(x)
取最低位操作 x & 1
离散化
802.区间和
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 300010;
int a[N],s[N],n,m;
vector<int> alls;
vector<PII> add,query;
int find(int x){
int l = 0, r = alls.size()-1;
while(l<r){
int mid = l+r >>1;
if (alls[mid]>=x){
r=mid;
}
else{
l = mid+1;
}
}
return r+1;
}
int main(){
cin>>n>>m;
for (int i = 0; i < n; ++i) {
int x,c;
cin>>x>>c;
add.push_back({x,c});
alls.push_back(x);
}
for (int i = 0; i < m; ++i) {
int l,r;
cin>>l>>r;
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(), alls.end()),alls.end());
for (auto item:add) {
int x = find(item.first);
a[x]+=item.second;
}
for (int i = 1; i <= alls.size() ; ++i) {
s[i] = s[i-1]+a[i];
}
for (auto item:query) {
int r = find(item.second),l= find(item.first);
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
难点
用到前缀和求区间内值的和。(数太多,超出界限)
首先去重:先将数组进行排序,利用unique,erase去重。
之后二分查找。
最后构造离散后的大数组。
区间合并
803.区间和并
按照区间左端点排序。
初始化左st右ed边界为-2e9。
比较ed与当前所在区间左端点,更新区间,并将更新后的区间记录下来。
返回数组长度。
注意引用的应该加&
void merge(vector<PII> &seg){
vector<PII> res;
sort(seg.begin(),seg.end());
int st = -2e9,ed = -2e9;
for (auto item:seg) {
if (ed < item.first){
if (st!=-2e9) res.push_back({st,ed});
st = item.first,ed = item.second;
}
else {
ed = max(ed,item.second);
}
}
if (st!=-2e9) res.push_back({st,ed});
seg = res;
}
加油!拼搏百天!
HEBUT 作者:陈冰yunshangjin
邮箱:suchenbin@163.com 可以发邮件进行交流