动态规划
背包问题
01背包问题
思路
动态规划:原问题分解为相对简单的子问题的方式求解复杂问题的方法
1状态表示(集合,属性)
2状态转移方程(状态计算,集合划分过程)
3边界确定
i:前i个物品,j:背包目前总体积。所有从前i个物品中选,且总体积不超或j的选法(集合)
f[i][j]:总价值
1.不放入第i个物品:f[i][j]=f[i-1][j]
2.放入第i个物品:f[i][j]=f[i-1][j-v[i]]+V[i]
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+V[i])
边界:j>=v[i]
i=1;i<=N;i++
j=0;j<=V;j++
if(j>=v[i])
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+V[i]);
else f[i][j]=f[i-1][j];
或
f[i][j]=f[i-1][j];
if(j>=v[i])
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
int res=0;
for(int i=0;i<=V;i++)
res=max(res,f[N][i]);
printf("%d\n",res);
或
printf("%d\n",f[N][V]);
滚动数组优化:
让j从大到小遍历
完整代码
//暴力
#include<stdio.h>
int N,V;
const int n=1010;
int v[n],w[n];
int f[n][n];
int max(int a,int b)
{
if(a>=b)
return a;
else return b;
}
int main()
{
int max(int a,int b);
scanf("%d %d",&N,&V);
for(int i=1;i<=N;i++)
{
scanf("%d %d",&v[i],&w[i]);
}
int res=0;
for(int i=1;i<=N;i++)
for(int j=0;j<=V;j++)
{
// if(j>=v[i])
// f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
// else f[i][j]=f[i-1][j];
f[i][j]=f[i-1][j];
if(j>=v[i])
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
// for(int i=0;i<=V;i++)
// res=max(res,f[N][i]);
// printf("%d\n",res);
printf("%d\n",f[N][V]);
return 0;
}
//优化
#include<stdio.h>
int N,V;
const int n=1010;
int f[n];
int v[n],w[n];
int max(int a,int b)
{
if(a>=b)
return a;
else return b;
}
int main()
{
scanf("%d %d",&N,&V);
for(int i=1;i<=N;i++)
{
scanf("%d %d",&v[i],&w[i]);
}
for(int i=1;i<=N;i++)
for(int j=V;j>0;j--)
{
if(j>=v[i])
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
// int res=0;
// for(int j=0;j<=V;j++)
// res=max(res,f[j]);
// printf("%d\n",res);
printf("%d\n",f[V]);
return 0;
}
完全背包问题
思路
完全背包:每个物品可放入多次
第i个不放入:f[i][j]=f[i-1][j]
第i个放入1次:f[i][j]=f[i-1][j-v[i]]+w[i]
第i个放入2次:f[i][j]=f[i-1][j-2*v[i]]+2*w[i]
……
第i个放入k次:f[i][j]=f[i-1][j-k*v[i]]+k*w[i]
f[i][j] =max(f[i-1][j], f[i-1][j-v[i]]+w[i], f[i-1][j-2*v[i]]+2*w[i],……,f[i-1][j-k*v[i]]+k*w[i])
f[i][j-v[i]]=max(f[i-1][j-v[i]],f[i-1][j-2*v[i]]+w[i],f[i-1][j-3*v[i]]+2*w[i],……,f[i][j]=f[i-1][j-(k+1)*v[i]]+k*w[i])
so
f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])
优化:
j=v[i];j<=V;j++ //边界:j>=v[i]
f[j]=max(f[j],f[j-v[i]]+w[i])
01背包: f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]) //优化从大到小
完全背包:f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]) //优化从小到大
完整代码
//暴力
#include<stdio.h>
int N,V;
const int n=1010;
int v[n],w[n];
int f[n][n];
int max(int a,int b)
{
if(a>=b)
return a;
else return b;
}
int main()
{
scanf("%d %d",&N,&V);
for(int i=1;i<=N;i++)
scanf("%d %d",&v[i],&w[i]);
for(int i=1;i<=N;i++)
for(int j=0;j<=V;j++)
{
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
// if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]); //
// else f[i][j]=f[i-1][j];
}
printf("%d\n",f[N][V]);
return 0;
}
//优化
#include<stdio.h>
int N,V;
const int n=1010;
int v[n],w[n];
int f[n];
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
scanf("%d %d",&N,&V);
for(int i=1;i<=N;i++) scanf("%d %d",&v[i],&w[i]);
for(int i=1;i<=N;i++)
for(int j=v[i];j<=V;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);
printf("%d\n",f[V]);
return 0;
}
多重背包问题
思路
多重背包:第i个物品最多用s[i]个
01背包的扩展
状态表示:第i个物品可放入0,1,2...s[i]个
k:0-s[i]
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2*v[i]]+2*w[i]...f[i-1][j-k*v[i]]+k*w[i])
k从0开始循环,f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i])
优化
j从大到小,因为上式都是f[i-1]
f[j]=max(f[j],f[j-k*v[i]]+k*w[i])
边界:k=0;k<=s[i]&&k*v[i]<=j;k++
完整代码
//暴力
#include<stdio.h>
int N,V;
const int n=110;
int v[n],w[n],s[n];
int f[n][n];
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
scanf("%d %d",&N,&V);
for(int i=1;i<=N;i++) scanf("%d %d %d",&v[i],&w[i],&s[i]);
for(int i=1;i<=N;i++)
for(int j=0;j<=V;j++)
for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
{
// f[i][j]=f[i-1][j];
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
printf("%d\n",f[N][V]);
return 0;
}
//优化
#include<stdio.h>
const int n=110;
int N,V;
int v[n],w[n],s[n];
int f[n];
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
scanf("%d %d",&N,&V);
for(int i=1;i<=N;i++) scanf("%d %d %d",&v[i],&w[i],&s[i]);
for(int i=1;i<=N;i++)
for(int j=V;j>=0;j--)
for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
printf("%d\n",f[V]);
return 0;
}
二进制优化思路
这二进制优化把人看傻了,发明二进制的人是个天才
任何数都能用二进制表示
8:1,2,4 8 >=2^3(8) 取2^2(4) ,c(2)
10:1,2,4 10 >=2^3(8) 取2^2(4) ,c(4)
200:1,2,4,8,16,32,64,73 200>=2^7(128) 取2^6(68),c(73)
将s个第i个物品 拆分为 n组(二进制表示)个第i个物品,将前n-1个i物品 变为第i个物品之前的单个物品
所以就转化为01背包问题
while(k<=s)
{
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
s=s-k;
k=k*2;
}
if(s>0)
{
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
优化代码
#include<stdio.h>
int N,V;
const int n1=25000,n2=2010;
int v[n1],w[n1];
int f[n2];
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
int cnt=0;
scanf("%d %d",&N,&V);
for(int i=1;i<=N;i++)
{
int a,b,s;
scanf("%d %d %d",&a,&b,&s);
int k=1;
while(k<=s)
{
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
s=s-k;
k=k*2;
}
if(s>0)
{
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
for(int i=1;i<=cnt;i++)
for(int j=V;j>=v[i];j--) f[j]=max(f[j],f[j-v[i]]+w[i]);
printf("%d",f[V]);
return 0;
}
分组背包问题
思路
分组背包:有N组物品,每组只能选一个
状态计算:
第i组选第一个:f[i][j]=f[i-1][j]
第i组选第k个:f[i][j]=f[i-1][j-v[i,k]]+w[i,k]
s[i]:每组个数(表示个数,注意边界)
完整代码
#include<stdio.h>
int N,V;
const int n=110;
int s[n];
int v[n][n],w[n][n];
int f[n];
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
scanf("%d %d",&N,&V);
for(int i=1;i<=N;i++)
{
scanf("%d",&s[i]);
for(int j=0;j<s[i];j++) scanf("%d %d",&v[i][j],&w[i][j]);
}
for(int i=1;i<=N;i++)
for(int j=V;j>=0;j--)
for(int k=0;k<s[i];k++)
{
if(j>=v[i][k])
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
}
printf("%d\n",f[V]);
return 0;
}
线性DP
数字三角形
最长上升子序列
思路
基础版
f[i]:以第i个数结尾的子序列的集合,f[i]表示子序列的长度
f[i]=f[i-1]+1
i=1;i<=N;i++
f[i]=1
j=1;j<=i;j++
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1)
优化版
优化版得先看一遍二分,整数二分边界问题真受够了
子序列长度从1开始,长度为1,长度为2...每个长度下以数字i结尾的子序列,只存i最小的子序列,存在q[i]中
//因为如果一个数能排在3后面,就一定能排在1后面。当长度一定时,子序列结尾的数字 ,只要记录一个数字最小的子序列
a[i]:数字
遍历a[i],二分法找a[i]可以跟在哪个数字(q[i])后面
int len=0,l=0,r=len;
if(q[mid]<a[i]) l=mid //答案在左边<a[i]的部分,但是不包含a[i]
else r=mid+1
len=max(len,r+1) //r是二分答案,是最大<a[i]的数,a[i]应该接在r后面,a[i]一旦接在r后面,子序列长度+1,以a[i]为结尾的最长子序列应属于r+1,len是右边界
完整代码
//基础版
#include<stdio.h>
int N;
const int n=1010;
int a[n],f[n];
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;i++) scanf("%d",&a[i]);
for(int i=1;i<=N;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1); //卧草好聪明的办法
}
}
int res=0;
for(int i=1;i<=N;i++)
res=max(res,f[i]);
printf("%d\n",res);
return 0;
}
//优化版
#include<stdio.h>
int N;
const int n=100010;
int a[n],q[n];
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
scanf("%d",&N);
for(int i=0;i<N;i++) scanf("%d",&a[i]); //二分法只能从0开始循环
for(int i=0;i<N;i++) q[i]=a[i];
int len=0;
q[0]=-2e9; //先给q[0]赋值,第一次for只会用到q[0],
for(int i=0;i<N;i++)
{
int l=0,r=len;
while(r-l>0)
{
int mid=(l+r+1)/2;
if(q[mid]<a[i]) l=mid; //第一次循环q[mid]=q[0],已经赋值过了
else r=mid-1;
}
len=max(len,r+1);
q[r+1]=a[i]; //这一步就相当于给q[]赋值了
}
printf("%d\n",len);
return 0;
}
最长公共子序列
思路
状态表示
f[i][j]:第一个序列a[]前i个数字的子序列、第二个子序列b[]前j个数字的子序列、构成的所有公共子序列(集合)的长度最大值
状态计算
用01表示最长公共子序列是否包含a[i]、b[j]
00:f[i][j]=f[i-1][j-1]
01:f[i-1][j]
//01:子序列不包含a[i],包含b[j]
//f[i-1][j]:a[]中前i-1个数字的子序列,b[]中前j个数字的子序列,构成的公共子序列(集合)的长度最大值
/*所以f[i-1][j]一定不包含a[i],有包含b[j]也有不包含b[j]
所以f[i-1][j]>01*/
//max(a,b,c)=max(max(a,b),max(b,c))
10:f[i][j-1] //同理
11:f[i-1][j-1]+1
//+1而不是+2,因为只有a[i]==b[j]才会出现11
i=1;i<=N
j=1;j<=M
{
f[i][j]=max(f[i][j-1],f[i-1][j])
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1)
!!!注意
scanf("%c",&a[i])会读取空格,换行符
只能用scanf("%s",a+1)
完整代码
#include<stdio.h>
int M,N;
const int n=1010;
char a[n],b[n];
int f[n][n];
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
scanf("%d %d",&N,&M);
// for(int i=1;i<=n;i++) scanf("%c",&a[i]);
// for(int i=1;i<=M;i++) scanf("%c",&b[i]);
// scanf%c会在每次读取字符时保留换行符或空白字符,应该直接读取整个字符串,而不是逐个字符读取
scanf("%s",a+1);
scanf("%s",b+1);
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(a[i]==b[j])
f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
}
printf("%d\n",f[N][M]);
return 0;
}
最短编辑距离
思路
状态表示:集合:方法。把A[i]序列变为B[j]序列需要的所有方法的集合
状态计算:1.删:f[i-1][j]+1
2.增:f[i][j-1]+1
3.改:f[i-1][j-1]+1
初始化
for (int i = 0; i <= n; i++) f[i][0] = i;
for (int i = 0; i <= n; i++) f[0][i] = i;
3.if(a[i] == b[i]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
完整代码
#include<stdio.h>
const int N = 1010; //这行必须放在char A[N]前,,我记得以前不用啊
int n, m;
char A[N], B[N]; //注意char类型
int f[N][N];
int min(int a, int b)
{
if(a <= b) return a;
else return b;
}
int main()
{
scanf("%d %s", &n, A + 1);
scanf("%d %s", &m, B + 1);
for (int i = 0; i <= n; i++) f[i][0] = i;
for (int j = 0; j <= m; j++) f[0][j] = j;
for (int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
if(A[i] == B[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
printf("%d\n", f[n][m]);
return 0;
}
基础算法
快速排序
y总说二分法的边界让人蛋疼,我觉得快排也是,所有的边界问题都很蛋疼
思路
快排用的是分治的思想
问题分解,解决,合并
数组放入q[n]中,边界随意
分解:
设x等于任意数最好是中间值 int x = q[(a + b)/2]
解决:
指针i,j指向数组一头一尾(如果打算先do再while,i = a - 1, j = b + 1)
while(i <= j) i++,j--后直接交换,注意交换前先if(i <= j)判断
合并:
递归 sort(a,j,q);sort(i,b,q);
边界问题,两个模板:
1.int i = 0; i < n; i++
do i++; while(i < j)
sort(a,j,q);sort(j + 1, b, q)
2.while(i <= j) if(i <= j) sort(a,j,q);sort(i,b,q)
完整代码
#include<stdio.h>
const int N = 100010;
int n,q[N];
void sort(int a,int b, int q[])
{ //判定数组有数 if(a > b) return;
if(a >= b) return;
int x = q[(a + b) / 2]; //注意x
int i = a, j = b;
while(i <= j) //边界
{
while(q[i] < x) i++;
while(q[j] > x) j--;
if(i <= j)
{
int temp = q[i];
q[i] = q[j];
q[j] = temp;
i ++,j--; //
}
}
sort(a,j,q); //先while(i <= j)后++,--,递归只能是j,i
sort(i,b,q); //y总先do,用while(i < j),递归只能用j,j + 1
}
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i ++) //从0开始也可
{
scanf("%d",&q[i]);
}
sort(1,n,q);
for(int i = 1; i <= n; i++)
printf("%d ",q[i]);
printf("\n");
return 0;
}
#include<iostream>
using namespace std;
#define N 100010
int n,q[N];
void quick_sort(int q[],int l, int r)
{
if(l>=r) return; //防止数组没有数
//int x =q[l + r >> 1];
int x = q[(l + r)/2];
/*
如果这里是x = l,底下递归是j,j + 1
如果这里是x = r,底下递归是i - 1,i
如果是别的数无所谓
*/
int i = l - 1, j = r + 1; //先do后判断,所以多一点
int swap;
while(i < j)
{
do i ++; while(q[i] < x);
do j --; while(q[j] > x);
if(i < j)
{
swap = q[i];
q[i] = q[j];
q[j] = swap;
}
}
quick_sort(q, l, j); //边界问题
quick_sort(q, j + 1, r);
}
int main()
{
scanf("%d",&n);
for(int i = 0; i < n; i ++) //从0开始
{
scanf("%d",&q[i]);
}
quick_sort(q,0,n-1);
for(int i = 0; i < n; i ++)
printf("%d ",q[i]);
return 0;
}
24.11.9.这道题我写了三个多小时,它是一种最基础最简单的算法,但是让人蛋疼的区间问题直到最后也没能完全理解清楚,只总结出了两个不同区间的代码模板、
有点怀疑学习算法的正确方法,对我这样只想有个基础能力目标蓝桥杯省一的人来说,钻研每一个细小的问题(没有老师同伴解惑)或许太耗时耗力。
y总已经帮我们总结好了模板,避开了坑避开了初学者的难点只有最基础的算法思路,听课,理解思路,背模板,刷题才是我应该做的。
y总的课真的没有一句废话
第k个数
四个半小时
#include <iostream>
using namespace std;
int n,k;
const int N = 100010;
int q[N];
void sort(int a,int b,int q[]) //int sort(int a, int b, int q[], int k)
{
if(a >= b) return ; //return q[a];
int x = q[(a + b)/2];
int i = a - 1,j = b + 1;
while(i < j)
{
do i++; while(q[i] < x);
do j--; while(q[j] > x);
if(i < j)
{
int temp = q[i];
q[i] = q[j];
q[j] = temp;
}
}
sort(a,j,q);
sort(j + 1, b, q);
// if(j - a + 1 >= k) return sort(a,j,q,k);
// else return sort(j + 1, b, q, k -(j - a + 1));
}
int main()
{
scanf("%d %d",&n,&k);
for(int i = 1; i <= n ; i++) scanf("%d",&q[i]);
// cout << sort(0, n - 1, q, k) << endl;
sort(1, n, q);
printf("%d\n", q[k]);
return 0;
}
归并排序
24.11.10两个小时
思路
归并排序本质还是分治
但是先递归后解决
找中间点:
快排找的是数组中的一个数,这个数一般取数组中的中间值 int x = q[(l + r)/2];
归并排序找的是位于数组中间位置的一个中间位置 int mid = (l + r)/2;
递归:
sort(q, l, mid); sort(q, mid + 1, r);
解决:
两个指针分别指向两段递归好的有序数列的最小值 int k = 0, i = l, j = mid + 1;
最小值存放在空数组tmp[],i++或j++,k++
存放两段递归好的有序数列的最小值
模板:
while(i <= mid && j <= r)
{
if(q[i] <= q[j]) tmp[k++] = q[i++];
else tmp[k++] = q[j++];
}
while(i <= mid) tmp[k++] = q[i++];
while(j <= r) tmp[k++] = q[j++];
for(int i = l, k = 0; i <= r; i++, k++) q[i] = tmp[k];
完整代码
#include<stdio.h>
int n;
const int N = 100010;
int q[N],tmp[N];
void sort(int q[], int l, int r)
{
if(l >= r) return;
int mid = (l + r)/2;
int i = l, j = mid + 1, k = 0;
sort(q, l, mid); //先递归后解决
sort(q, mid + 1, r);
while(i <= mid && j <= r) //模板从这里开始背
{
if(q[i] <= q[j]) tmp[k++] = q[i++];
else tmp[k++] = q[j++];
}
while(i <= mid) tmp[k++] = q[i++];
while(j <= r) tmp[k++] = q[j++];
for(int i = l, k = 0; i <= r; i++, k++) q[i] = tmp[k]; //注意l,r
}
int main()
{
scanf("%d",&n);
for(int i = 0; i < n; i++) scanf("%d", &q[i]);
sort(q, 0, n - 1);
for(int i = 0; i < n; i++) printf("%d ", q[i]);
return 0;
}
逆序对的数量
//wocao好聪明的办法
#include<iostream>
using namespace std;
const int N = 100010;
int q[N],tmp[N];
long long sort(int q[], int l, int r)
{
if(l >= r) return 0; //0
int mid = (l + r)/2;
long long res = sort(q, l, mid) + sort(q, mid + 1, r); //
int i = l, j = mid + 1,k = 0;
while(i <= mid && j <= r)
{
if(q[i] <= q[j]) tmp[k++] = q[i ++];
else {
res += mid - i + 1; //
tmp[k ++] = q[j ++];
}
}
while(i <= mid) tmp[k ++] = q[i ++];
while(j <= r) tmp[k ++] = q[j ++];
for(int i = l, k = 0; i <= r; i ++, k ++) q[i] = tmp[k]; //<=
return res;
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 0; i < n; i ++) scanf("%d", &q[i]);
// int m = sort(q, 0, n - 1);
// printf("%d ", m); 不能这么写
cout << sort(q, 0, n - 1) << endl;
return 0;
}
//这代码没问题能提交但是输出结果不对让我找了半天,,,,,
二分法
用y总的话来说,整数二分的边界问题,是真TM蛋疼
吸取上面教训,不要深究边界,直接背模板
24.11.18,1小时,之前写过,今天还算容易
思路
//整数
我觉得什么都别说了直接上模板吧
i=0;i<n;i++
q[i]
int l,r;
l=0,r=n-1
//左边界
while(l<r) //注意循环
mid=(l+r)/2
if(a[mid]>=k) r=mid //每次选答案所在的区间,区间一定要将答案覆盖掉
else l=mid+1
printf()
if()
//右边界
else {
int l=0,r=n-1
while()
mid=(l+r+1)/2
if(a[mid]<=k) l=mid;
r=mid-1
printf()
//浮点数
取后六位:while(r-l>1e-8)
浮点数二分比整数二分舒服多了
完整代码
//整数
#include<stdio.h>
int n,q;
const int N=100010;
int a[N];
int main()
{
scanf("%d %d",&n,&q);
// for(int i=1;i<=n;i++)
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
while(q--)
{
int k;
scanf("%d",&k);
int l,r;
// l=1,r=n;
l=0,r=n-1;
while(l<r)
{
int mid=(l+r)/2;
if(a[mid]>=k) r=mid;
//每次选答案所在的区间,区间一定要将答案覆盖掉
else l=mid+1;
}
if(a[l]!=k) printf("-1 -1\n");
else{
printf("%d ",l);
// int l=1,r=n;
l=0,r=n-1;
while(l<r)
{
int mid=(l+r+1)/2;
if(a[mid]<=k) l=mid;
else r=mid-1;
}
printf("%d\n",l);
}
}
return 0;
}
//浮点数
//求一个数的三次方根
#include<iostream>
using namespace std;
double n,k;
int main()
{
scanf("%lf",&n);
double l = -100, r = 100; //n取值-10000到+10000
//0-max(1,x),r一定大于1(0.01 = 0.1*0.1)
while(r - l > 1e-8) //还有这里,多两位数
{
double mid = (l + r)/2;
double k = mid;
if(k * k * k < n) l = k;
else r = k;
}
printf("%.6lf",l);
return 0;
}
前缀和
思路
2024.11.23,模板很简单,1.5小时,二维数组稍微费点心思
数组a[n],第l到r个数的和
= s[r] - s[l - 1]
求s[n]
s[i] = s[i - 1] + a[i]
二维
a[x1][y1]到a[x2][y2]的和 //a[n][m] n行m列
画图
s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
求s[i][j]
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i-1][j-1] + a[i][j]
完整代码
//挺简单但是容易粗心
#include<iostream>
using namespace std;
const int N = 100010;
int a[N], s[N];
int l, r;
int main()
{
int n, m;
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i];
while(m --)
{
scanf("%d %d", &l, &r);
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}
//y总模板真niub
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N], s[N][N];
int main()
{
int n, m, q;
scanf("%d %d %d", &n, &m, &q);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j++)
scanf("%d", &a[i][j]); //读入s[i][j]也行,底下相对改
for(int i = 1; i <= n; i ++) //注意这一对循环放while外面,不然会超时
for(int j = 1; j <= m; j ++)
s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + a[i][j]; //相对改成s[i][j] += 后面去掉a[i][j]
while(q--)
{
int x1, y1, x2, y2;
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]); //注意-1边界问题
}
return 0;
}
//自己写的,不对,找不到错,输出结果仍然是0,不管了背模板
// #include<iostream>
// using namespace std;
// const int N = 1010;
// int a[N][N], s[N][N];
// int main()
// {
// int m, n, q;
// scanf("%d %d %d", &n, &m, &q);
// while(n --)
// {
// int j = 1;
// for(int i = 1; i <= m; i ++) scanf("%d", &a[j][i]);
// j ++;
// }
// for(int i = 1; i <= n; i ++)
// for(int j = 1; j <= m; j ++)
// {
// s[i][j] = s[i - 1][j - 1] + a[i - 1][j] + s[i][j - 1] + a[i][j];
// }
// int x1, y1, x2, y2;
// while(q --)
// {
// scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
// s[x2][y2] = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
// printf("%d\n", s[x2][y2]);
// }
// return 0;
// }
差分
2024.11.24.2h
一维的还好,二维头疼
是谁闲的没事能想出差分这个概念,又是谁能想出差分解这道题太聪明了
思路
原数组a[n]
构造数组b[n]
a[n]是b[n]的前缀和
b[n]叫做a[n]的差分
模板
void insert(int l, int r, int c) //l,r位置,c需要加的数
{
b[l] += c;
b[r + 1] -=c;
}
//读入a[n]两个for
for(int i = 1; i <= n; i ++)
insert(i, i, a) //构造b[n],想不出来草稿纸过一遍
while(q --)
insert(l, r, c) //给b[n]插入c
//输出b[n]前缀和
二维
//右下角部分
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= c;
b[x2 + 1][y2 + 1] +=c;
}
//读入a[n]
//构造b[n]
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
insert(i, j, i ,j ,a)
while
insert(x1, y1, x2, y2, c); //插入c
//输出 前缀和
b[i][j] += b[i - 1][j] + b[i][j - 1] -b[i - 1][j - 1];
二维是真头疼
完整代码
//这道题用差分太聪明了
//是谁能闲的没事能想到差分这个概念
//当我觉得能想到用差分已经很牛逼的时候,这个构造差分的insert更让我觉得我们不是同一个物种
#include<iostream>
using namespace std;
const int N = 100010;
int a[N], b[N];
void insert (int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
int n, m;
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]); //构造b[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]; //b[i]的前缀和是a[i]
for(int i = 1; i <= n; i ++) printf("%d ",b[i]);
return 0;
}
//一维都很简单,二维脑子很疼
/*
主要是思路乱了
下次先在纸上写整体思路
a[i][j]读入A
构造b[i][j]
b[i][j]插入,这里重点
算b[i][j]前缀和就是
*/
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x1][y2 + 1] -= c; // 右下所有-c
b[x2 + 1][y1] -=c;
b[x2 + 1][y2 + 1] += c;
}
int main()
{
int n, m, q;
scanf("%d %d %d", &n, &m, &q);
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, j, i, j, a[i][j]); //构造
while(q --)
{
int x1, y1, x2, y2, c;
scanf("%d %d %d %d %d",&x1, &y1, &x2, &y2, &c);
insert(x1, y1, x2, 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]; //前缀和
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j++)
printf("%d ",b[i][j]);
printf("\n"); //
}
return 0;
}
双指针
思路
for(int i = , j = ;i < ;i++)
{
。。。
while(j < i && ...)
{
...
j++;
}
...
}
注意c++不能用printf
完整代码
#include<iostream>
using namespace std;
const int N = 100010;
int n,a[N],q[N];
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
int res = 0;
for(int i = 1, j = 1; i <= n; i ++)
{
q[a[i]] ++;
while(j < i && q[a[i]] > 1)
{
q[a[j ++]] --; //
}
res = max(i - j + 1,res);
}
printf("%d", res);
return 0;
}
位运算
25.3.4
思路
位运算两种类型(二进制)
1.求n的第k个数:n >> k & 1 (n右移k与1)
2.返回N的最后一个1:lowbit(n) = n & -n (函数)
eg:1010(10) lowbit(10) = 10
101000(40) lowbit(40) = 1000
n & -n相当于n & n的反码+1(n的补码)
原理不记了反正记住函数
int lowbit(int n)
{
return n & -n ;
}
可以返回n的最后一位1
完整代码
#include<iostream>
using namespace std;
int n;
int lowbit(int x)
{
return x & -x;
}
int main()
{
cin >> n;
while(n --)
{
int x;
cin >> x;
int res = 0;
while(x) x -= lowbit(x), res ++; //每次减去x的最后一位1
// printf("%d\n", res);
cout << res << ' '; //
}
return 0;
}
数据结构
单链表
25.3.6 23:25好快啊25年都到3月了
1小时多点吧没算,以前写过不费劲
概念
注意:
1.初始化
void init()
{
idx = 0;
head = -1;
}
2.增加链表,idx ++;
3.删除注意可能是头结点,head = ne[head]
4.输出链表
for(int i = head; i != -1; i = ne[i]) cout << e[i]
这里i != -1对吗,head不就等于-1,这个循环能执行?
能执行是因为有过删除头结点的操作吗,如果没有过删除头结点的操作,输出单链表就不能这么写是吗?不太懂
代码
#include<iostream>
using namespace std;
const int N = 100010;
int e[N], ne[N];
int idx, head;
void init()
{
idx = 0;
head = -1;
}
void add_head(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx ++; //++
}
void dele(int x)
{
ne[x - 1] = ne[ne[x - 1]];
}
void insert(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k - 1];
ne[k - 1] = idx++; //++
}
int main()
{
init();
int M;
cin >> M;
while(M--)
{
char ch;
int x, k;
cin >> ch;
if(ch == 'H')
{
cin >> x;
add_head(x);
}
else if(ch == 'D')
{
cin >> x;
if(x == 0) head = ne[head]; //
else dele(x);
}
else{
cin >> k >> x;
insert(k, x);
}
}
int i = head;
// while(ne[i])
// {
// cout << e[i] << ' ';
// i = ne[i];
// }
for(int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
//这里i != -1对吗,head不就等于-1,这个循环能执行?
//能执行是因为有过删除头结点的操作吗,如果没有过删除头结点的操作,输出单链表就不能这么写是吗?不太懂
return 0;
}
双链表
25.3.7 0:28,1小时,简单
概念
双链表:一个节点储存两个指针,一个指向前le[],一个指向后re[]
1.没有头结点尾结点,用下标0和1代替,所有链表在下标0和1之间,idx从2开始
2.输出双链表
for(int i = 0; i != 1; i = re[i]) cout << e[i];
应当I是不指向结尾
代码
#include<iostream>
using namespace std;
const int N = 100010;
int idx, re[N], le[N], e[N], M;
void init()
{
// idx = 0;
// head = 0;
// tail = 1;
// re[head] = 1;
// le[tail] = 0;
idx = 2;
re[0] = 1; //相当于没有头结点和尾结点了 链表在0和1中间
le[1] = 0;
}
void rightadd(int k,int x)
{
e[idx] = x;
re[idx] = re[k];
le[idx] = k;
le[re[k]] = idx;
re[k] = idx ++;
}
void dele(int k)
{
re[le[k]] = re[k];
le[re[k]] = le[k];
}
int main()
{
init();
cin >> M;
while(M --)
{
//string op;
char ch,ch2;
int x, k;
cin >> ch;
if(ch == 'L')
{
cin >> x;
rightadd(0, x);
}
else if(ch == 'R')
{
cin >> x;
rightadd(le[1], x);
}
else if(ch == 'D')
{
cin >> x;
dele(x + 1); //注意下标
}
else if(ch == 'I')
{
cin >> ch2;
if(ch2 == 'L')
{
cin >> k >> x;
rightadd(le[k + 1], x);
}
else{
cin >> k >> x;
rightadd(k + 1, x);
}
}
}
for(int i = re[0]; i != 1; i = re[i]) cout << e[i] << ' '; //i !=结尾
cout <<endl;
return 0;
}
栈和队列
刚打算说简单的我看到了第二题,,,开始背吧
队列2025.3.12.8:59,概念不难,栈也是
概念
栈:先进后出,有底罐子
队列:先进先出,一口进一口出管子
栈
int a[N],tt;
加入:a[++tt] = x
删除:tt --
判断是否为空:if(tt > 0) no
栈顶元素:a[tt]
队列
int q[N], hh, tt = -1;
插入:q[++ tt] = x;
删除:hh ++;
判断是否为空:if(tt >= hh) no
队列头元素尾元素:q[hh], q[tt]
单调栈
代码
队列有道题代码有点复杂,表达式求值
#include <iostream>
#include <cstring> //<string>.size<cctype>isdigit
#include <algorithm> //STL算法
#include <stack> //stack容器,可以使用栈
#include <unordered_map> //无序映射,类似哈希表,存入运算符优先级
using namespace std;
stack<int> num; //定义数字栈
stack<char> op; //定义运算符栈
void eval()
{
auto b = num.top(); num.pop(); //.top查看栈顶元素
auto a = num.top(); num.pop(); //.pop移除栈顶元素
auto c = op.top(); op.pop();
int x;
if (c == '+') x = a + b;
else if (c == '-') x = a - b;
else if (c == '*') x = a * b;
else x = a / b;
num.push(x);
}
int main()
{
unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
string str;
cin >> str;
for (int i = 0; i < str.size(); i ++ )
{
auto c = str[i];
if (isdigit(c)) //c是否为0-9的数字
{
int x = 0, j = i;
while (j < str.size() && isdigit(str[j]))
x = x * 10 + str[j ++ ] - '0'; //
i = j - 1;
num.push(x); //.push存储
}
else if (c == '(') op.push(c);
else if (c == ')')
{
while (op.top() != '(') eval(); //
op.pop();
}
else
{
while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();
op.push(c); //
}
}
while (op.size()) eval(); //
cout << num.top() << endl;
return 0;
}
单调栈
25.3.12 9:40这道题有点意思
#include<iostream>
using namespace std;
int N;
const int n = 100010;
int q[n], tt;
int main()
{
cin >> N;
while(N --)
{
int x;
cin >> x;
while(tt && q[tt] >= x) tt --; //&&
if(tt) cout << q[tt] << ' ';
else cout << "-1" << ' ';
q[++ tt] = x;
}
return 0;
}
单调队列
25.3.12 15:40这题有点小难度
#include<iostream>
using namespace std;
const int N = 1000010;
int n, k, a[N], q[N]; //q[N]储存单调数列的下标(滑动窗口数列)
int main()
{
scanf("%d %d", &n,&k);
for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
int hh = 0, tt = -1; //hh还是要初始化的
for(int i = 0; i < n; i ++ )
{
//让队头滑出窗口
if(hh <= tt && q[hh] < i - k + 1) hh++;
//单调队列
while(hh <= tt && a[q[tt]] >= a[i]) tt --; //=
q[ ++ tt] = i;
if(i >= k - 1) cout << a[q[hh]] << ' ';
}
puts (""); //
//最大值
hh = 0, tt = -1;
for(int i = 0; i < n; i ++)
{
if(hh <= tt && q[hh] <= i - k) hh ++;
while(hh <= tt && a[q[tt]] <= a[i]) tt --;
q[++ tt] = i;
if(i >= k - 1) cout << a[q[hh]] << ' ';
}
return 0;
}
KMP
woc哪个天才想出的KMP啊
25.3.13 12:53有仨小时吧
就不解释了直接看代码吧
代码
#include<iostream>
using namespace std;
int n,m;
const int N = 100010, M = 1000010;
char p[N], s[M];
int ne[N];
int main()
{
cin >> n >> p + 1 >> m >> s + 1; //下标从1开始
//next数组 ne[i] = j;以i结尾的后缀数列,向右移,直到匹配前缀数列长度为j,下标为j
for(int i = 2, j = 0; i <= n; i ++) //字符串指针i是从1开始,j都从0开始
{
while(j && p[i] != p[j + 1]) j = ne[j]; //p, j = ne[j]
if(p[i] == p[j + 1]) j ++; //这里判断不需要j存在
ne[i] = j;
}
//KMP匹配
for(int i = 1, j = 0; i <= m; i ++)
{
while(j && s[i] != p[j + 1]) j = ne[j];
if(s[i] == p[j + 1]) j ++;
if(j == n) //匹配成功
{
printf("%d ", i - n); //题目下标从0开始
j = ne[j]; //ne j
}
}
return 0;
}
Trie
好难好难好难,简单的只有前面那一点是吧
中午看完的。25.3.13 23:00 1+0.5h
异或运算25.3.14 16:57挺久的仨小时?以后还是刚看完就写吧,不然太浪费时间,虽然这样记得牢
思路
以节点树的形式存储字符串
idx为每个节点唯一的序号
son[p][u] = ++idx存储树,p为当前节点序号idx(我理解的p是指针指向idx),u为下方子节点,等于子节点++idx的序号
cnt[p]表示字符串结尾数量
str[]为要插入或者查询的字符串
插入:
void insert(char str[])
{
int p = 0;
for(int i = 0; str[i]; i ++) 不管是插入还是查找,str[i](前一个字母存在)(插进去或找到),在进行下一个字母
{
int u = str[i] - 'a'; 让字母转化为数字存储
if(!son[p][u]) son[p][u] = ++idx; 不存在就加个新标识(idx)
p = son[p][u]; 插入完或本身就存在,p指向下一个节点(子节点)的序号
}
cnt[p]++; 节点p结尾的字符串数量=cnt[p]
}
查找:
int quar(char str[])
{
int p = 0;
for(int i = 0; str[i]; i ++)
{
int u = str[i] - 'a';
if(!son[p][u]) return 0; 从根节点开始遍历的,如果第一个字母就不存在,字符串肯定也不存在
p = son[p][u]; 指向下一个节点后i++
}
return cnt[p]; 遍历完字符串后p指向字符串结尾,返回单词数量
代码
#include<iostream>
using namespace std;
const int N = 100010;
char str[N];
int son[N][26], cnt[N], idx;
void insert(char str[])
{
int p = 0;
for(int i = 0; str[i]; i ++) //
{
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++idx; //
p = son[p][u];
}
cnt[p] ++;
}
int quar(char str[]) //
{
int p = 0;
for(int i = 0; str[i]; i ++) //
{
int u = str[i] - 'a';
if(!son[p][u]) return 0; //
p = son[p][u];
}
return cnt[p]; //
}
int main()
{
int n;
scanf("%d", &n);
while(n --)
{
char ch;
cin >> ch >> str;
if(ch == 'I') insert(str);
else cout << quar(str) << endl;
}
return 0;
}
异或运算:两个数二进制,不一样的为1,一样的为0,最后的值
#include<iostream>
//#include<algorithm>//
using namespace std;
const int N = 100010, M = 3100010; //M
int a[N];
int n;
int son[M][2], idx; //2
void insert(int x) //
{
int p = 0;
for(int i = 30; i >= 0; i --) //从最高位开始遍历,int通常32位整数
{
// if(!son[p][x >> i & 1]) son[p][x >> i & 1] = ++idx; //x右移i位取最低位,求二进制的第i位
// p = son[p][x >> i & 1];
int &s = son[p][x >> i & 1];
if(!s) s = ++idx;
p = s;
}
}
//woc好聪明
int qmax(int x)
{
int p = 0, res = 0;
for(int i = 30; i >= 0; i --)
{
int s = x >> i & 1;
if(son[p][!s])
{
res += 1 << i; //在res中加上2^i,相当于res的第i位加1
p = son[p][!s]; //!s
}
else p = son[p][s]; //
}
return res;
}
int main()
{
int res = 0; //
scanf("%d", &n);
for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
for(int i = 0; i < n; i ++) insert(a[i]);
for(int i = 0; i < n; i ++) res = max(res, qmax(a[i])); //woc这太聪明了
printf("%d\n", res);
return 0;
}
并查集
一道比一道难,最后一道真的写了好几天,视频至少两遍25.3.16 14:53
概念
1.集合合并
2.询问两个元素是否在一个集合中
每个集合用一棵树表示;树根的编号是整个集合的编号;每个节点存储它的父节点p[x]表示父节点
判断树根:if(p[x] == x)
求x的集合编号:while(p[x] != x) x = p[x];
if(p[x] != x) p[x] = find(x);
return p[x];
合并两个集合:p[x] = y
p[find(a)] = find(b);
代码
#include<iostream>
using namespace std;
const int N = 100010;
int q[N]; //q[N]等于N节点的父节点
int find(int x) //find查找这个数根节点
{
// int p[N];
if(q[x] != x) q[x] = find(q[x]); !
return q[x];
}
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++) q[i] = i; !1 //一开始每个数各自在一个集合中
while(m--)
{
char op;
int a, b;
cin >> op >> a >> b;
if(op == 'M')
{
q[find(a)] = find(b); !
}
else {
if(find(a) == find(b)) cout << "Yes" <<endl;
else cout << "No" << endl;
}
}
return 0;
}
食物链
#include<iostream>
using namespace std;
const int N = 50010;
int n, m;
int p[N], d[N]; //N的父节点,N到根节点的距离
/* d[x]
%3 = 1, 可以吃根节点
%3 = 2, 可以被根节点吃
%3 = 0, 跟根节点同类
*/
int find(int x) //x的根节点
{
if(p[x] != x)
{
int t = find(p[x]);
d[x] += d[p[x]];
//初始状态每个节点的父节点都是自己,p[x] = x;d[x] = 0;(d[x]到父节点的距离 = 0)
//路径压缩:p[x] = find(p[x]); d[x] = d[x] + d[p[x]];
p[x] = t;
}
return p[x];
}
int main()
{
scanf("%d%d",&n, &m);
for(int i = 1; i <= n; i ++) p[i] = i;
int res = 0;
while(m --)
{
int t, x, y;
scanf("%d%d%d", &t, &x, &y);
if(x > n || y > n) res ++; //或
else {
int px = find(x), py = find(y);
if(t == 1) //同类
{
if(px == py && (d[x] - d[y]) % 3) res ++;
else if(px !=py)
{
p[px] = py;
d[px] = d[y] - d[x]; //d[x] + ? = d[y],同类%3=0
}
}
else { //x吃y(x2,y1)
if(px == py && (d[x] - d[y] -1) % 3) res ++; //x2 y1
else if(px != py)
{
p[px] = py;
d[px] = d[y] + 1 - d[x]; //d[x] + ? - 1 = d[y]
}
}
}
}
printf("%d\n", res);
return 0;
}
堆
25.3.18 18:08我真服了,手机用了一下clash,然后流量热点全进不了csdn,还以为是csdn崩了,从3.16晚上到现在,才靠deepseek解决
具体原因猜的,用clash不正确,导致临时配置冲突或者残留,clash关了也没用,让csdn把ip封锁了?反正,先卸载手机clash,浏览器清理缓存,浏览器能使用,手机不行。手机重启,然后可以使用。可能不至于ip封锁,就是什么残留吧,浏览器一清手机一重启,重新分配IP就行
堆我都有点忘了,数据结构后面这些都有点难,得复习,但是没时间了,4.13蓝桥杯,我想在4月前把基础算法看完
思路
从trie开始好像都是数?记不清了回来复习再补上
根节点x,左下2x,右下2x+1,也是123的排序
手写堆:
插入一个数: heap[ ++ size] = x; up(size);
求集合当中最小值:heap[1];
删除最小值: heap[1] = heap[size]; size --; down(1);
删除任意一个元素: heap[k] = heap[size]; size --; down(k); up(k);
修改任意一个元素: heap[k] = x; don(k); up(k);
原理好简单,但我记得题好难,直接上代码吧
代码
#include<iostream>
using namespace std;
const int N = 100010;
int n, m;
int h[N], cnt;
void down(int x)
{
int t = x;
if(2 * x <= cnt && h[t] > h[2 * x]) t = 2 * x;
if(2 * x + 1 <= cnt && h[t] > h[2 * x + 1]) t = 2 * x + 1;
if(x != t)
{
swap(h[x], h[t]);
down(t);
}
}
int main()
{
cin >> n >> m;
cnt = n;
for(int i = 1; i <= n; i ++) scanf("%d", &h[i]);
for(int i = n / 2; i; i --) down(i);
while(m --)
{
printf("%d ", h[1]);
h[1] = h[cnt];
cnt --;
down(1);
}
return 0;
}
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int N = 100010;
int h[N], cnt, ph[N], hp[N], n, idx;
void heap_swap(int a, int b)
{
swap(ph[hp[a]], ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int x)
{
int t = x;
if(2 * x <= cnt && h[2 * x] < h[t]) t = 2 * x; //注意x和t
if(2 * x + 1 <= cnt && h[2 * x + 1] < h[t]) t = 2 * x + 1;
if(t != x)
{
heap_swap(x, t);
down(t);
}
}
void up(int x)
{
while(x / 2 && h[x / 2] > h[x])
{
heap_swap(x, x/2);
x >>= 1; // x = x / 2
}
}
int main()
{
cnt = 0, idx = 0;
cin >> n;
while(n --)
{
char op[5];
cin >> op;
int x, y;
if(!strcmp(op,"I"))
{
cin >> x;
ph[++idx] = ++ cnt; //注意这里idx和cnt是从0还是1开始
hp[cnt] = idx;
h[cnt] = x;
up(cnt);
}
else if (!strcmp(op,"PM")) printf("%d\n", h[1]);
else if(!strcmp(op, "DM"))
{
heap_swap(1, cnt);
cnt --;
down(1);
}
else if(!strcmp(op, "D"))
{
cin >> x;
x = ph[x];
heap_swap(x, cnt);
cnt --;
down(x);
up(x);
}
else
{
cin >> x >> y;
x = ph[x];
h[x] = y;
down(x);
up(x);
}
}
return 0;
}
//不是特别的难但是有点绕,自我感觉写完要疯掉了,还好有deepseek,不然得检查多久,真的会崩
哈希表
哈希表才是快学疯了,折腾整整一两天才把概念写出来,还不怎么记得了
概念
哈希表是将一组大范围数,映射到小范围数组中(h[]),它的存储结构有:
开放寻址法:找茅坑。这个用的最多但是初始化那里不太懂,null,memset,还有16进制
拉链法(单链表法):h[]为每个链表的头结点,这个注意也要初始化为空(!不是0)
代码
//链表法(拉链法)
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100003; //大于它的最小质数
int h[N], e[N], ne[N], idx;
void insert(int x)
{
int k = (x % N + N) % N; //注意N
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++;
}
bool quar(int x)
{
int k = (x % N + N) % N; //注意N
for(int i = h[k]; i != -1;i = ne[i])
{
if(e[i] == x) return true; // 这里用true,false
//else if(i == n) return k; 等循环完了就行,这里不用判断
}
return false;
}
int main()
{
int n;
cin >> n;
memset(h, -1, sizeof h); //数组初始化为空ctring
/*
数组如果定义为全局变量,默认已经初始化为0
如果在函数内部定义(main),此时 h 的元素是程序运行前内存区域的残留值(随机数)
memset覆盖掉之前初始化0,将H初始化为-1
*/
while(n --)
{
char op;
int x;
cin >> op >> x;
if(op == 'I') insert(x);
else if(op == 'Q')
{
if(quar(x)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}
//开放寻址法
#include<iostream>
#include<cstring>
using namespace std;
const int N = 200003; //两倍,比他大的最小质数
int n, h[N];
const int null = 0x3f3f3f3f; //
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x) //null
{
t ++ ;
if (t == N) t = 0;
}
return t;
}
int main()
{
memset(h, 0x3f, sizeof h); //
cin >> n;
while(n --)
{
char op;
int x;
cin >> op >> x;
if(op == 'I') h[find(x)] = x;
else if(op == 'Q')
{
if(h[find(x)] != x) puts("No");
else puts("Yes");
}
}
return 0;
}
字符串哈希
这个更疯,注意力不集中,没太看懂
23.3.18 20:37杀疯了21:05
为什么这个记得这么详细,因为真的没太懂,也不是没太懂,根本记不住原理过程,直接背模版吧
原理
字符串前缀哈希法
str = "abcabcabcabc"
h[0] = 0
h[1] = "a"的哈希值
h[2] = "ab"的哈希值
h[3] = "abc"的哈希值
哈希值是什么
p进制
a b c d = (1 2 3 4)p = 1xp^3 + 2xp^2 + 3xp^1 + 4xp^0
% Q
p = 131或13331, Q = 2^64基本不存在冲突
1..........L - 1 L.........R
左边高位,右边低位(参考二进制)
h[R] = (axp^r-1 .....axp^0)p
h[L - 1] = (axp^L-1 ....... axp^0)p
R与L - 1相差R - (L - 1)位,将他们对齐:h[L - 1]*p^(R - L + 1)
eg:(bbbbbbbaaaaaa)
(aaaaaaa)
变成(bbbbbbaaaaaa)
(aaaaaa000000)
不需要取模,溢出相当于取模(没懂)
插入:h[i] = h[i - 1] * p + str[i] //woc这是真牛逼,什么人能想出来字符串哈希这种东西
快速判断两个区间内字符串是否完全相同:求前缀,算两个字符串的哈希值,哈希值相同,字符串就相同
模版
#include<iostream>
using namespace std;
const int N = 100010; //P放在这里也行
typedef unsigned long long ULL;
int n, m;
char str[N]; //读入原数组
int P = 131; //16进制
int h[N], p[N]; //h[]存储哈希值,p[N]相当于P的N次方
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
cin >> n >> m;
cin >> str + 1;
p[0] = 1; //初始化
//读入h[] p[]
for(int i = 1; i <=n; i ++)
{
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + str[i];
}
while(m --)
{
int l1, l2, r1, r2;
cin >> l1 >> r1 >> l2 >> r2;
if(get(l1, r1) != get(l2, r2)) cout << "No" << endl;
else cout << "Yes" << endl;
}
return 0;
}
千言万语就剩了一句woc
搜索与图论
dfs
25.3.20 18:23两天前写的好像,忘了保存。递归画图有点难理解。直接上皇后问题吧
算是深度搜索?遍历完整条路再回溯
模版(皇后问题)
第二种方法更快更熟一点
//第一种方法,每个格子枚举
#include<iostream>
using namespace std;
const int N = 10;
int n;
bool row[N], col[N], dg[N], udg[N];
char g[N][N];
void dfs(int x, int y, int s)
{
if(s > n) return; //s存储已放皇后量
if(y == n) y = 0, x ++;
if(x == n) //
{
if(s == n)
{
for(int i = 0; i < n; i ++) puts(g[i]);
puts("");
}
return;
}
// if(x == n && s == n)
// {
// for(int i = 0; i < n; i ++) puts(g[i]);
// puts("");
// return;
// }这段return的逻辑不对,x到n就一定要return,不然溢出
g[x][y] = '.'; //初始化
dfs(x, y + 1, s); //下一位
if(!row[x] && !col[y] && !dg[x + y] && !udg[n + x - y])
{
g[x][y] = 'Q';
row[x] = col[y] = dg[x + y] = udg[n + x - y] = true;
dfs(x, y + 1, s + 1); //
row[x] = col[y] = dg[x + y] = udg[n + x - y] = false;
g[x][y] = '.';
}
}
int main()
{
cin >> n;
dfs(0, 0, 0);
return 0;
}
//第二种顺序搜索,固定行往不同列中插入
#include<iostream>
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool col[N], dg[N], udg[N]; //
void dfs(int u)
{
if(u == n)
{
for(int i = 0; i < n; i ++) //
//cout << g[u][i]; u永远=n,可以用双重循环输出
puts(g[i]);
puts("");
return;
}
for(int i = 0; i < n; i ++) //
{
if(!col[i] && !dg[u - i + n] && !udg[u + i]) //这里xy轴可以互换
{
g[u][i] = 'Q';
col[i] = dg[n + u - i] = udg[u + i] = true;
dfs(u + 1);
col[i] = dg[n + u - i] = udg[u + i] = false;
g[u][i] = '.';
}
}
}
int main()
{
cin >> n;
for(int i = 0; i < n; i ++) //
for(int j = 0; j < n; j ++)
g[i][j] = '.';
dfs(0); //dfs永远是从0开始的,数组定义也是
return 0;
}
bfs
25.3.21 12:40有空学下stl,感觉这个有点像背代码了
皇后问题最后是熟了,但是bfs真的不行,也是我不想看了,就当都不会吧,第二题跳过
(25.3.25 23:39补)这段代码不要看了,涉及太多stl背的难受,后面有更简洁的bfs模板,看这个
概念
广度遍历,一层一层向外扩,可搜索最短路径
队列:
typedef pair<int, int> PII;
int bfs()
{
queue<PII> q; //创建
q.push({0, 0});
while()
auto t = q.front;
q.pop();
代码
#include<iostream>
#include<cstring> //memset
#include<queue> //队列
#include <algorithm>
using namespace std;
typedef pair<int, int> PII; //队列
const int N = 110;
int n, m;
int g[N][N], d[N][N];
int bfs()
{
queue<PII> q; //创建空队列
memset(d, -1, sizeof d); //
d[0][0] = 0;
q.push({0, 0}); // 起点入队
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; //上右下左
while(q.size())
{
auto t = q.front();
q.pop();
for(int i = 0; i < 4; i ++)
{
int x = t.first + dx[i], y = t.second + dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
{
d[x][y] = d[t.first][t.second] + 1;
q.push({x, y});
}
}
}
return d[n - 1][m - 1];
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j ++)
cin >> g[i][j];
cout << bfs() << endl;
return 0;
}
25.3.25 17:01 刚从数据结构开始大概看了看,学是学完了,但是记不住啊
树图dfs,bfs
存储
有向图:
1.邻接矩阵(几乎不用)
g[a][b]存储a到b的信息,如果有权重存储的是权重,没有的话是bool,true表示有边,false表示没边
邻接矩阵不能存储重边
2.邻接表
跟哈希表一样,每个点都有自己的单链表,链表的点代表可以指向的点
链表头h[],连接新的点插入到链表头
dfs
模板
//树和图深度优先搜索的代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010, M = N * 2; //M
int h[N], e[M], ne[M], idx; //M,h[]链表头,e[]所有的边()
bool str[N];
void add(int a, int b) //有向图or无向图 树的创建,插入数,边
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
void dfs(int u) //注意void
{
str[u] = true;
for(int i = h[u]; i != -1; i = ne[i]) //背就完了
{
int j = e[i];
if(!str[j]) int s = dfs(j);
}
}
int main()
{
memset(h, -1, sizeof h); //头结点指向-1
dfs(1);
return 0;
}
代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010, M = N * 2; //M
int h[N], e[M], ne[M], idx, n; //M
int ans = N;
bool str[N];
void add(int a, int b) //有向图or无向图 树的创建,插入数,边
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
//返回以u为根的点数
int dfs(int u)
{
int sum = 1, res = 0; //sum总点数, res以u为根最大点数
str[u] = true;
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if(!str[j])
{
int s = dfs(j); //
res = max(res, s);
sum += s;
}
}
res = max(res, n - sum);
ans = min(ans, res); //这里是min,所以ans必须初始化最大
return sum;
}
int main()
{
cin >> n;
memset(h, -1, sizeof h); //
for(int i = 0; i < n - 1; i ++) //
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a); //
}
dfs(1);
cout << ans << endl;
return 0;
}
bfs
框架
入队 queue
while queue不空
{
t = 队头
拓展t所有邻点x
if(x未遍历)
{
x入队
d[x] = d[t] + 1
}
}
模板
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N], idx; //有向图单边,不需要2倍的M
int q[N], d[N]; //bfs本质是队列,q[N]存储队列,d[N]存储距离
int bfs()
{
int tt, hh; //hh队头, tt队尾
//初始化
q[0] = 1; //队列从节点1开始
d[1] = 0; //节点1的初始距离为0
memset(d, -1, sizeof d);
//逻辑
while(hh <= tt)
{
int t = q[hh ++]; //从队头遍历队列
for(int i = h[t]; i != -1; i = ne[i]) //遍历每个队列的链表
{
int j = e[i]; //先记录链表的值(指向的节点)
if(d[t] == -1) //因为初始化距离为-1 ,-1就是没有加入队列中
{
d[t] = d[j] + 1; //记录d[t]距离根节点的距离
q[++ tt] = j; //将j(节点的值)加入队尾
}
}
}
return d[N]; //返回到n的最短路径
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
cout << bfs() << endl;
return 0;
}
拓扑排序
25.3.27 11:53有向无环图
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N]; //q为拓扑排序的队列,d为入的个数
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
bool topsort()
{
int hh = 0, tt = -1; //
for(int i = 1; i <= n; i ++) //入为0的都加进队列
if(!d[i]) q[++ tt] = i;
while(hh <= tt)
{
int t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
d[j] --; //
if(d[j] == 0) //j
q[++ tt] = j; //
}
}
return tt == n - 1; //
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++)
{
int x, y;
cin >> x >> y;
add(x, y);
d[y] ++;
}
if(topsort())
{
for(int i = 0; i < n; i++)
cout << q[i] << " ";
}
else puts("-1");
return 0;
}