基础算法模板
来源:AcWing
快速排序
#include <iostream>
using namespace std;
const int N = 1e5 +10;
int q[N];
int n;
void quick_sort(int q[],int l,int r)
{
if (l>=r) return;
int x = q[l],i = l-1,j = r+1;
while(i<j)
{
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if (i<j)
{
swap(q[i],q[j]);
}
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
int main()
{
scanf("%d",&n);
for (int i=0;i<n;i++)
scanf("%d",&q[i]);
quick_sort(q,0,n-1);
for (int i=0;i<n;i++)
{
printf("%d ",q[i]);
}
return 0;
}
归并排序
#include <iostream>
using namespace std;
int n;
const int N=1e5+10;
int q[N];
int tmp[N];
void merge_sort(int q[],int l,int r)
{
if (l>=r)return;
int mid = l+r>>1;
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
int k=0,i=l,j=mid+1;
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 (i=l,j=0;i<=r;i++,j++)
{
q[i] = tmp[j];
}
}
int main()
{
scanf("%d",&n);
for (int i = 0;i<n;i++)
{
scanf("%d",&q[i]);
}
merge_sort(q,0,n-1);
for (int i=0;i<n;i++)
{
printf("%d ",q[i]);
}
return 0;
}
整数二分
数的范围
#include <iostream>
#include<cstdio>
using namespace std;
int n,q;
int a[100010];
int mid;
int main()
{
int k;
scanf("%d %d",&n,&q);
for (int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
while(q--)
{
scanf("%d",&k);
int l = 0,r = n-1;
while(l<r){
mid = l+r>>1;//刚开始把这个放到了循环外,二分过程根本没开始,导致超时
if (a[mid]>=k)
{
r = mid;
}
else l = mid+1;
}
if (a[l]!=k)
{
printf("-1 -1\n");
continue;
}
else{
printf("%d ",l);
l=0;
r=n-1;
while(l<r)
{
mid = l+r+1>>1;
if (a[mid]<=k)
{
l=mid;
}
else{
r = mid-1;
}
}
printf("%d\n",l);
}
}
return 0;
}
浮点数二分
三次方根
//浮点数二分的题
#include <iostream>
#include<cstdio>
using namespace std;
double n;
int main()
{
cin>>n;
double l=-10000,r=10000,mid;
while(r-l>1e-8)
{
mid = (r+l)/2;
if (mid*mid*mid>=n)//check函数可以通过三次方的方式进行判断
{
r = mid;
}
else {
l = mid;
}
}
printf("%.6f",l);
return 0;
}
高精度加法
#include <iostream>
#include <vector>
using namespace std;
vector<int> add(vector<int> &A,vector<int> &B)
{
vector<int> C;//C=A+B
int t = 0;//进位位
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) C.push_back(1);//如果最后仍有进位,将1压入栈中
return C;
}
int main()
{
string a,b;
vector<int> A,B;
cin>>a>>b;
for (int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');//减去‘0’,将其转换为int型整数,否则是字符
for (int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
auto C = add(A,B);//C++新标准,自动识别C的类型
for (int i=C.size()-1;i>=0;i--)
{
cout<<C[i];
}
return 0;
}
高精度减法
#include<iostream>
#include <vector>
using namespace std;
bool cmp(vector<int> &A,vector<int> &B)
{
if (A.size()!= B.size()) return A.size()>B.size();
for (int i=A.size()-1;i>=0;i--)
{
if (A[i]!=B[i])
return A[i]>B[i];
}
return true;
}
vector<int> sub(vector<int> &A,vector<int> &B)
{
vector<int> C;
int t=0;
for (int i=0,t=0;i<A.size();i++)
{
t = A[i]-t;
if (i<B.size()) t-=B[i];
C.push_back((t+10)%10);
if (t<0) t=1;// 如果不够减,高位借位1
else//如果够减,不用借位
{
t = 0;
}
}
while(C.size()>1&&C.back() == 0) C.pop_back();//去除前导零,从1开始
return C;
}
int main()
{
string a,b;
cin>>a>>b;
vector<int> A,B;
for(int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--) B.push_back(b[i]-'0');
if (cmp(A,B))
{
auto C=sub(A,B);
for (int i=C.size()-1;i>=0;i--) cout<<C[i];
}
else
{
auto C =sub(B,A);
printf("-");
for (int i=C.size()-1;i>=0;i--) cout<<C[i];
}
return 0;
}
高精度乘法
#include<iostream>
#include<vector>
using namespace std;
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;
}
while(C.size()>1&&C.back()==0) C.pop_back();
return C;
}
int main()
{
string a;
int b;
vector<int> A;
cin>>a>>b;
for (int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
auto C = mul(A,b);
for (int i = C.size()-1;i>=0;i--) printf("%d",C[i]);
return 0;
}
高精度除法
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> div(vector<int> &A,int b,int &r)//r是引用,因此虽然没把r返回,但是还是能在main函数中使用
{
vector<int> C;
r = 0;
for (int i=A.size()-1;i>=0;i--)//对于除法来说,在C中结果是倒着存的
{
r = 10*r+A[i];
C.push_back(r/b);
r %= b;
}
reverse(C.begin(),C.end());
while(C.size()>1&&C.back() == 0) C.pop_back();//去除前导零
return C;
}
int main()
{
int b;
vector<int> A;
string a;
int r ;//输出余数
cin>>a>>b;
for (int i=a.size()-1;i>=0;i--) A.push_back(a[i]-'0');
auto C = div(A,b,r);
for (int i=C.size()-1;i>=0;i--) printf("%d",C[i]);
cout<<endl<<r<<endl;
return 0;
}
高精度除法需要注意:倒着存储,需要逆序,乘法加法减法都是从低位开始计算,除法从高位开始计算,需要reverse,在计算时从size-1开始。
一维前缀和
前缀和:用于求一段数组元素的和
初步处理如下:
s[i] = s[i-1]+a[i];
s[n]即为存放前缀和的数列
要求l,r之间的数的和,
s[r]-s[l-1];
注意此处l要减1
//一维前缀和,s[i] = s[i-1] + a[i];
#include<iostream>
using namespace std;
const int N=100010;
int a[N],s[N];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for (int i=1;i<=n;i++)//都从1开始
{
scanf("%d",&a[i]);
}
for (int i=1;i<=n;i++)
{
s[i] = s[i-1] + a[i];
}
while(m--)
{
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",s[r]-s[l-1]);
}
return 0;
}
二维前缀和
//二维前缀和
#include<iostream>
const int N = 1010;
int m,n,q;
int a[N][N],s[N][N];
int main()
{
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++)
s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];//二位前缀和的公式
while(q--)
{
int x1,x2,y1,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]);//二位前缀和面积公式
}
return 0;
}
一维差分
不太好懂的写法
#include<iostream>
using namespace std;
const int N = 100010;
int a[N],b[N];
int n,m;
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);//对差分数组进行操作,对l以后,r以前的数进行+C操作,对r以后的数组进行-c操作
}
for (int i=1;i<=n;i++) b[i] += b[i-1];//求前缀和
for (int i=1;i<=n;i++)
printf("%d ",b[i]);
}
比较好懂的写法
#include<iostream>
using namespace std;
const int N = 100010;
int a[N],b[N];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i] = a[i] - a[i-1];//构造差分数组
}
while(m--)
{
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
b[l]+=c;//对差分数组进行操作,对l以后,r以前的数进行+C操作,对r以后的数组进行-c操作
b[r+1]-=c;
}
for (int i=1;i<=n;i++) a[i] = b[i]+a[i-1];//求前缀和,a数组是b数组的前缀和,b数组是a数组的差分,可以通过对b求前缀和得到a,而前缀和例子中,s为a的前缀和,s[i] = s[i-1]+a[i],公式为由a[i]得到s[i]
for (int i=1;i<=n;i++)
printf("%d ",a[i]);
}
一个比较好理解的题解
二维差分
一个比较好懂的题
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N],b[N][N];
int n,m,q;
void insert(int x1,int y1,int x2,int y2,int c)
{
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
int main()
{
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]);//构造差分数组b
while(q--)
{
int x1,y1,x2,y2,c;
cin>>x1>>y1>>x2>>y2>>c;
insert(x1,y1,x2,y2,c);//对差分数组b进行+c操作,实际是对a数组的每个元素进行了分别加c,因为a数组是b数组的前缀和
}
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];//这里b数组原本已经存储了a[i][j]所以对比前缀和公式不同,这里复用了b,其实a[i][j]已经计入,最后结果存放在b中
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
cout<<b[i][j]<<" ";
cout<<endl;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WEMQrPzk-1676292897998)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1648093005037.png)]
双指针算法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i4jfsICy-1676292897998)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1648093247938.png)]
例子1:输入一串字符,以空格间隔,输出每个单词
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GopkXPUv-1676292898002)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1648093413238.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quTOIpnI-1676292898002)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1648093551572.png)]
最长连续不重复子序列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mHV82lQm-1676292898003)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1648093951690.png)]
红色 i
绿色 j
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6YA6uC0V-1676292898003)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1648431317923.png)]
#include<iostream>
using namespace std;
const int N = 100010;
int a[N];
int s[N];
int main()
{
int n;
cin>>n;
int res = 0;
for (int i=0;i<n;i++)
{
cin>>a[i];
}
for (int i=0,j=0;i<n;i++){
s[a[i]]++;//相应下标元素加1用于统计每个元素出现次数
while(s[a[i]]>1)//当有重复元素时,重复元素必为a[i]需要将j移到a[i]位置
{
s[a[j]]--;
j++;
}
res = max(res,i-j+1);
}
cout<<res;
}
判断子序列
#include<iostream>
using namespace std;
const int N = 100010;
int a[N],b[N];
int n,m,x;
int main()
{
cin>>n>>m>>x;
for (int i=0;i<n;i++)
cin>>a[i];
for (int j=0;j<m;j++)
cin>>b[j];
int j = m-1;
int i=0;
for(i=0;i<n;i++)
{
while(j>=0&&a[i]+b[j]>x)
{
j--;
}
if (a[i]+b[j] == x)
cout<<i<<' '<<j;
}
}
位运算
-
求第k位二进制表示中数字是几
n>>k&1
-
lowbit(x):返回x的最后一位1
x&-x;
用于统计x里1的个数
#include<iostream>
using namespace std;
const int N = 100010;
int lowbit(int x)//可以得到最后一位1,比如数字为1100,返回100
{
return x&-x;
}
int a[N];
int main()
{
int n;
cin>>n;
for (int i =0;i<n;i++)
{
cin>>a[i];
}
for (int i=0;i<n;i++)
{
int x=a[i];
int res = 0;
while(x) x-=lowbit(x),res++;//每次把最后一位1减去,统计1的个数
cout<<res<<' ';
}
return 0;
}
离散化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aM4cpSze-1676292898004)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1648560629422.png)]
1:46往前一点
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N = 300010;//n次插入,m次询问,前缀和
int a[N],s[N];//a数组用于存放具体的数据,s数组是a数组的前缀和
int m,n;
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)/2;
if (alls[mid]>=x)r = mid;
else l = mid+1;
}
return r+1;//前缀和数组下标从1开始比较好处理
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
{
int x,c;
cin>>x>>c;
add.push_back({x,c});
alls.push_back(x);
}
for (int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
//alls去重
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
//插入具体数据到a[N]中
for (auto item:add)
{
int x = find(item.first);
a[x] += item.second;
}
//对a数组求前缀和
for(int i=1;i<=alls.size();i++)s[i] = s[i-1]+a[i];
//执行m次询问
for(auto item:query)
{
int l = find(item.first);
int r = find(item.second);
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
区间合并
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int,int> PII;
vector<PII> segs;//用于存放区间
void merge(vector<PII> &segs)
{
vector<PII> res;
int st = -2e9,ed = 2e9;
sort(segs.begin(),segs.end());
for (auto seg:segs)
{
if (ed<seg.first)//如果遍历的区间在当前维护区间的右边
{
if (st!=-2e9)//并且不是临界的区间
{
res.push_back({st,ed});
st = seg.first;
ed = seg.second;
}
}
else//有交集的情况
{
ed = max(ed,seg.second);
}
}
if (st !=-2e9) res.push_back({st,ed});
segs = res;
}
int main()
{
int n;
scanf("%d",&n);
int l,r;
for (int i=1;i<=n;i++)
{
cin>>l>>r;
segs.push_back({l,r});
}
merge(segs);
cout<<segs.size()<<endl;
}
pair的基本用法
#include <utility>
//定义
pair<T1, T2> p1; //创建一个空的pair对象(使用默认构造),它的两个元素分别是T1和T2类型,采用值初始化。
pair<T1, T2> p1(v1, v2); //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。
make_pair(v1, v2); // 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型。
p1 < p2; // 两个pair对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。
p1 == p2; // 如果两个对象的first和second依次相等,则这两个对象相等;该运算使用元素的==操作符。
p1.first; // 返回对象p1中名为first的公有数据成员
p1.second; // 返回对象p1中名为second的公有数据成员
//创建和初始化
pair<string, string> anon; // 创建一个空对象anon,两个元素类型都是string
pair<string, int> word_count; // 创建一个空对象 word_count, 两个元素类型分别是string和int类型
pair<string, vector<int> > line; // 创建一个空对象line,两个元素类型分别是string和vector类型
pair<string, string> author("James","Joy"); // 创建一个author对象,两个元素类型分别为string类型,并默认初始值为James和Joy。
pair<string, int> name_age("Tom", 18);
pair<string, int> name_age2(name_age); // 拷贝构造初始化
//多个相同类型的pair对象
typedef pair<string,string> Author;
Author proust("March","Proust");
Author Joy("James","Joy");
//访问
pair<int ,double> p1;
p1.first = 1;
p1.second = 2.5;
cout<<p1.first<<' '<<p1.second<<endl;
//输出结果:1 2.5
string firstBook;
if(author.first=="James" && author.second=="Joy")
firstBook="Stephen Hero";
//在某些函数会以pair对象作为返回值时,可以直接通过std::tie进行接收。比如:
std::pair<std::string, int> getPreson() {
return std::make_pair("Sven", 25);
}
int main(int argc, char **argv) {
std::string name;
int ages;
std::tie(name, ages) = getPreson();
std::cout << "name: " << name << ", ages: " << ages << std::endl;
return 0;
}
数组模拟链表(速度快,new很慢容易超时)
单链表46分钟左右
//head表示头结点的下标
//e[i]表示节点i的值
//ne[i]表示节点i的next指针是多少
//idx存储当前已经用到哪个点
int head,e[N],ne[N],idx;
//初始化
void init()
{
head =-1;
idx = 0;
}
//头插法
void add_to_head(int x)
{
ne[idx] = head;
head = idx;
e[idx] = x;
idx++;
}
//插入元素到下标是k的元素后面
void add(int k,int x)
{
e[idx] =x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
//删除元素,将下标是K的点后面的点删除
void remove(int k)
{
ne[k] = ne[ne[k]];
}
单链表
#include<iostream>
using namespace std;
const int N = 100010;
int e[N],ne[N],head,idx;
int m;
void init()
{
head = -1;
idx = 0;
}
void add_to_head(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx++;
}
void remove(int k)
{
ne[k] = ne[ne[k]];
}
void add(int k,int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
int main()
{
cin>>m;
init();
while(m--)
{
char op;
cin>>op;
if (op == 'H')
{
int x;
cin>>x;
add_to_head(x);
}
if(op == 'D')
{
int k;
cin>>k;
if (!k) head = ne[head];
remove(k-1);
}
if (op == 'I')
{
int x,k;
cin>>k>>x;
add(k-1,x);
}
}
for (int i=head;i!=-1;i = ne[i])
{
cout<<e[i]<<' ';
}
return 0;
}
双链表
0号点表示head,1号点表示tail
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f2k9gljA-1676292898004)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1649315596425.png)]
#include<iostream>
using namespace std;
const int N = 100010;
int e[N],l[N],r[N],idx;
//0表示head,1表示tail
void init(){
r[0] = 1;//head的右指针指向tail
l[1] = 0;//tail的左指针指向head
idx = 2;//因为0与1已被占用
}
void add(int k,int x)
{
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx;
idx++;
}
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main()
{
int m;
cin>>m;
init();
while(m--)
{
string op;
cin>>op;
int k,x;
if (op == "L")
{
cin>>x;
add(0,x);
}
else if (op == "R")
{
cin>>x;
add(l[1],x);
}
else if (op == "D")
{
cin>>k;
remove(k+1);
}
else if (op == "IR")
{
cin>>k>>x;
add(k+1,x);
}
else
{
cin>>k>>x;
add(l[k+1],x);
}
}
for (int i=r[0];i!=1;i=r[i])
{
cout<<e[i]<<' ';
}
cout<<endl;
}
数组模拟栈
#include<iostream>
using namespace std;
const int N = 100010;
int skt[N],tt;//stk[M]表示栈,tt表示栈顶指针
string op;
int main()
{
int m;
tt = -1;
cin>>m;
while(m--)
{
cin>>op;
if (op == "push")//压栈
{
int x;
cin>>x;
skt[++tt] = x;
}
else if(op == "pop")//出栈
{
tt--;
}
else if(op == "empty")//判空
{
if (tt==-1)
{
cout<<"YES"<<endl;
}
else
{
cout<<"NO"<<endl;
}
}
else if (op == "query")//查询
{
cout<<skt[tt]<<endl;
}
}
}
表达式求值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qDftvvYI-1676292898005)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1660016156491.png)]
#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<stack>
#include<cstring>
#include<vector>
using namespace std;
stack<int> num;//存数字的栈
stack<char> op;//存操作符的栈
unordered_map<char,int> pr{{'+',1},{'-',1},{'*',2},{'/',2}};//存储运算符优先级用于判断子树是否遍历完
void eval()
{
int b = num.top();num.pop();
int a = num.top();num.pop();
char c = op.top();op.pop();
if (c == '+') num.push(a+b);
else if (c == '-') num.push(a-b);
else if (c == '*') num.push(a*b);
else num.push(a/b);
}
int main()
{
string str;
cin>>str;
//提取数字压栈
for (int i=0;i<str.size();i++)
{
char c = str[i];
if (isdigit(c))
{
int j=i;
int x = 0;
while(j<str.size()&&isdigit(str[j]))
{
x = x*10 + str[j]-'0';
j++;
}
i = j-1;//从j-1位置开始前面的数字处理结束
num.push(x);
}
else if (c == '(')
{
op.push(c);
}
else if (c == ')')
{
while(op.top()!='(') eval();
op.pop();
}
else{
while(op.size()&&pr[op.top()]>=pr[c])
{
eval();
}
op.push(c);
}
}
while(op.size())eval();
cout<<num.top()<<endl;
return 0;
}
数组模拟队列
int q[N],hh,tt = -1;//hh队头,tt队尾
//插入
q[++tt] = x;
//弹出
hh++;
//取队头元素
q[hh];
//取队尾元素
q[tt];
//判断空
if (hh<=tt) not empty
#include<iostream>
using namespace std;
const int N = 100010;
int q[N],hh = 0,tt = -1;//hh为队头,tt为队尾,队尾插入元素,队头弹出元素
int main()
{
int m;
cin>>m;
while (m--)
{
string op;
cin>>op;
if (op =="pop")
{
hh++;//队头删除一个元素,注意是++
}
else if (op == "push")
{
int x;
cin>>x;
q[++tt] = x;//队尾入队
}
else if (op == "empty")
{
if (hh<=tt) cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
else
{
cout<<q[hh]<<endl;
}
}
}
单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1;i<=n;i++)
{
while(tt&&check(q[tt],i))tt--;
stk[++tt] = i;
}
#include<iostream>
using namespace std;
const int N = 100010;
int stk[N],tt;//栈顶指针从0开始
int main()
{
int x;
int n;
cin>>n;
while(n--)
{
cin>>x;
while (tt&&stk[tt]>=x)//栈不空
{
tt--;//如果栈顶元素比当前元素大,那栈顶元素就出栈,为保持单调性
}
if (tt)
{
cout<<stk[tt]<<' ';//如果栈顶元素比当前元素小,说明栈顶元素已经是最近最小的元素
}
else cout<<-1<<' ';
stk[++tt] = x;//x入栈,没啥影响,该出栈还是会出栈
}
}
单调队列(再看)
常见模型:找出滑动窗口中的最大值、最小值
int hh = 0,tt = -1;
for (int i=0;i<n;i++)
{
while(hh<=tt && check_out(q[hh]))hh++;
while(hh<=tt && check(q[tt],i))tt--;
q[++tt] = i;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YcdoAdy5-1676292898005)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1649746195400.png)]
1:53
#include<iostream>
using namespace std;
const int N = 1000010;
int a[N];//表示输入的数组
int q[N];//表示单调队列,存储的是下标
int n,k;//K为窗口大小
int main()
{
scanf("%d%d",&n,&k);
for (int i=0;i<n;i++)
scanf("%d",&a[i]);
int hh = 0,tt = -1;//初始化队列队头和队尾
//队头出队,超出滑动窗口长度
for (int i=0;i<n;i++)//此处i表示当前遍历下标
{
while(hh<=tt&&i-k+1>q[hh])//q[hh]表示当前队头的下标,hh<=tt队列非空
{
hh++;//队头出队
}
//队尾不单调递减
if (a[q[tt]]>=a[i]&&hh<=tt)//非空
tt--;
//插入元素
q[++tt] = i;//这里插入的是下标
if (i>=k-1)//i是从0开始的,必须至少有一个窗口的长度才能计算
printf("%d ",a[q[hh]]);
}
printf("\n");
//计算最大值
hh = 0;
tt = -1;
for (int i=0;i<n;i++)
{
if (hh<=tt&&i-k+1>q[hh])hh++;
if (a[q[tt]]<=a[i]&&hh<=tt)
{
tt--;
}
q[++tt] = i;
if (i>=k-1)printf("%d ",a[q[hh]]);
}
}
kmp
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GUKnuYKB-1676292898006)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1649814777021.png)]
2:25
#include<iostream>
using namespace std;
const int N = 100010;
const int M = 1000010;
char s[M],p[N];//s是字符串,p是模板串
int n,m;
int ne[N];
int main()
{
cin>>n>>p+1>>m>>s+1;
//求next数组
for (int i=2,j=0;i<=n;i++)//i从2开始是因为对模板串分析可知,如果只是从0,1开始,根本没有最长前缀和后缀没必要
{
while(j&&p[j+1]!=p[i])//j不能等于零(退无可退)并且在遍历模板串自己的时候出现了不匹配的情况
{
j = ne[j];//移动模板串到下一个匹配的位置
}
if (p[i] == p[j+1])j++;//如果匹配就往下
ne[i] = j;//将结果存下来
}
//进行匹配操作
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)
{
j = ne[j];
cout<<i-n<<' ';//下标从0开始
}
}
}
Trie树
作用:高效地存储和查找字符串 集合的数据结构
要么全大写,要么全小写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0CJUK53-1676292898006)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1649818362569.png)]
#include<iostream>
using namespace std;
const int N = 100010;
int son[N][26];//表示子节点
int idx=0;//表示当前遍历的子节点
int cnt[N];//表示以当前节点结尾的单词有多少个
void insert(char str[])
{
int p=0;//表示父节点
for (int i=0;str[i];i++)//因为字符串结尾是/0所以可以用来作为判断的条件
{
int u = str[i]-'a';//转换为0~25的数字
if (!son[p][u]) son[p][u] = ++idx;//如果没有节点就创建一个节点
p = son[p][u];
}
cnt[p]++;
}
int query(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;
cin>>n;
while(n--)
{
char op;
cin>>op;
if (op == 'Q')
{
char str[N];
cin>>str;
int t = query(str);
cout<<t<<endl;
}
else
{
char str[N];
cin>>str;
insert(str);
}
}
}
最大异或对
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PDWuZARh-1676292898007)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1660033966289.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NDsd3950-1676292898007)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1660034155397.png)]
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010, M = 3000000;
int idx;
int son[M][2];//M个头,2个分支
int n;
int a[N];
void insert(int x)
{
int p=0;
for (int i=30;i>=0;i--)
{
int &s = son[p][x>>i & 1];
if (!s)
{
s = ++idx;
}
p = s;
}
}
int query(int x)
{
int p = 0;
int res = 0;
for (int i=30;i>=0;i--)
{
int s = x>>i &1;
if (son[p][!s])
{
res += 1<<i;
p = son[p][!s];
}
else
{
p = son[p][s];
}
}
return res;
}
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
cin>>a[i];
insert(a[i]);
}
int res = 0;
for (int i=0;i<n;i++)
{
res = max(res,query(a[i]));
}
cout<<res<<endl;
return 0;
}
并查集
1.将两个集合合并
2.询问两个元素是否在一个集合中
近乎O(1)
基本原理:
每个集合用一棵树来表示。树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点
- 问题1:
如何判断树根?
if(p[x]==x)
- 问题2:
如何求x的集合编号?
while(p[x]!=x)x = p[x]
- 问题3:
如何合并两个集合?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QtGCCES8-1676292898007)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1651713358874.png)]
px是x的集合编号,py是y的集合编号,p[x] = y
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WKFZGqIf-1676292898008)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1651713489470.png)]
路径压缩
48:50
初始化操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QsKHOFBu-1676292898008)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1651714709640.png)]
路径压缩
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZXLS9rXx-1676292898008)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1651714750939.png)]
#include<iostream>
using namespace std;
const int N = 100010;
/*
基本原理:每个集合用一棵树来表示。树的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点
*/
int p[N];//用于存储集合编号,也就是该元素的父节点
int n,m;
int find(int x)//用于查找x元素所在集合的根节点,也就是x元素所在集合的集合编号,也就是集合的祖宗节点,并进行路径压缩(遍历得到
{
if(p[x]!=x) p[x] = find(p[x]);//父节点等于p[x]的父节点,x不是根节点,就让其父结点等于祖宗节点也就是根,进行了路径压缩,遍历到根节点后让所有子节点能够直达根节点
return p[x];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) p[i] = i;//初始化,当前节点的父节点指向自己
while(m--)
{
char op[2];//此处为一个小技巧,防止输入字符后带有空格等
int a,b;
scanf("%s%d%d",op,&a,&b);
if(op[0] == 'M')
{
p[find(a)] = find(b);//a的祖宗节点的父节点是b的祖宗节点
}
else if (op[0] == 'Q')
{
if (find(a)==find(b))
printf("Yes\n");
else
printf("No\n");
}
}
return 0;
}
连通块中点的数量
#include<iostream>
using namespace std;
int n,m;
const int N = 100010;
int p[N],cnt[N];
int find(int x)
{
if (p[x]!=x) p[x] = find(p[x]);
return p[x];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
p[i]=i;
cnt[i] = 1;
}
while(m--)
{
char op[5];
int a,b;
scanf("%s",op);
if(op[0] == 'C')
{
scanf("%d%d",&a,&b);
if (find(a) == find(b)) continue;
cnt[find(b)] += cnt[find(a)];
p[find(a)] = find(b);
}
else if (op[1] == '1')
{
scanf("%d%d",&a,&b);
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
else
{
scanf("%d",&a);
cout<<cnt[find(a)]<<endl;
}
}
}
食物链
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JB4gjgS6-1676292898009)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1660105512669.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbZHhxiv-1676292898009)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1660105648883.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h3LHsdD9-1676292898009)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1660106113078.png)]
#include<iostream>
using namespace std;
int n,m;
const int N = 100010;
int p[N],cnt[N];
int find(int x)
{
if (p[x]!=x) p[x] = find(p[x]);
return p[x];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
p[i]=i;
cnt[i] = 1;
}
while(m--)
{
char op[5];
int a,b;
scanf("%s",op);
if(op[0] == 'C')
{
scanf("%d%d",&a,&b);
if (find(a) == find(b)) continue;
cnt[find(b)] += cnt[find(a)];
p[find(a)] = find(b);
}
else if (op[1] == '1')
{
scanf("%d%d",&a,&b);
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
else
{
scanf("%d",&a);
cout<<cnt[find(a)]<<endl;
}
}
}
手写堆
需要实现的操作
下标从1开始
1.插入一个数 heap[++size] = x;up(size);
2.求集合当中的最小值 heap[1]
3.删除最小值 heap[1] = heap[size];size–;down(1);
4.删除任意一个元素 heap[k]=heap[size];size–;down(k);up(k);//down up只会执行一个
5.修改任意一个元素 heap[k] = x;down(k);up(k);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NOkaH3hm-1676292898010)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1652190420286.png)]
down一个数变大了需要往下移,up一个数变小需要往上移(小根堆)
#include<iostream>
using namespace std;
const int N = 100010;
int h[N],siz;//h[i]表示i位置的结点存储的元素的值,siz表示堆中元素个数,也表示最后一个元素的位置(下标从1开始)
int m,n;
void down(int u)//u是当前节点
{
int t = u;//t用于存储三个节点中最小的下标
if (u*2<=siz&&h[u*2]<h[t]) t = u*2;
if (u*2+1<=siz&&h[u*2+1]<h[t]) t = u*2+1;
if (u!=t)
{
swap(h[t],h[u]);
down(t);//如果t==u意味着不用变动,u就是三个结点中拥有最小值的结点下标,否则交换数值
//交换数值后,t这个结点存储原本u的值,u存储存储t的值(三个数中的最小值)。u不用调整了,但t情况不明,可能需要调整。直到它比左右子节点都小
}
}
void up(int u)//u是当前节点
{
while (u/2&&h[u/2]>h[u])//u/2存在,并且失衡
{
swap(h[u/2],h[u]);
u/=2;
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&h[i]);
siz = n;//初始化siz
//从n/2开始down,将二叉树初始化为小根堆
for (int i = n/2;i;i--)
{
down(i);
}
while(m--)
{
printf("%d ",h[1]);//从堆顶取出最小的元素
h[1] = h[siz];//堆顶的元素等于最后一个元素
siz--;
down(1);//对换上来的新的堆顶元素进行down操作,完成小根堆的维护
}
return 0;
}
模拟堆
C 库函数 int strcmp(const char *str1, const char *str2) 把 str1 所指向的字符串和 str2 所指向的字符串进行比较。
该函数返回值如下:
- 如果返回值小于 0,则表示 str1 小于 str2。
- 如果返回值大于 0,则表示 str1 大于 str2。
- 如果返回值等于 0,则表示 str1 等于 str2。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int N = 100010;
int h[N],siz=0,ph[N],hp[N];//ph指向堆中的位置,hp由位置指向第几次插入
void heap_swap(int a,int b)//a,b是位置
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a],hp[b]);
swap(h[a],h[b]);
}
void down(int u)
{
int t = u;
if (u*2<=siz&&h[u*2]<h[t]) t = u*2;
if (u*2+1<=siz&&h[u*2+1]<h[t]) t = u*2+1;
if (t!=u)
{
heap_swap(u,t);
down(t);
}
}
void up(int u)
{
while (u/2&&h[u/2]>h[u])
{
heap_swap(u,u/2);
u/=2;
}
}
int main()
{
int n,m=0;
scanf("%d",&n);
while(n--)
{
char op[10];
int k,x;
scanf("%s",op);
if (!strcmp(op,"I"))
{
scanf("%d",&x);
m++;//当前是第几个插入的数
siz++;//堆中元素增加了
h[siz] = x;
ph[m] = siz;//第m次插入的值对应的堆中的位置是siz
hp[siz] = m;//siz的位置所在的元素是第m次插入的
up(siz);//执行一次up把新插入的放在最后的位置上的元素放置到合适的位置
}
else if (!strcmp(op,"PM"))
{
printf("%d\n",h[1]);
}
else if (!strcmp(op,"DM"))
{
heap_swap(1,siz);
siz--;
down(1);
}
else if (!strcmp(op,"D"))
{
scanf("%d",&k);
k = ph[k];//ph中存储了第k个插入的数在堆中的位置
heap_swap(k,siz);
siz--;
up(k);
down(k);
}
else
{
scanf("%d%d",&k,&x);
k = ph[k];
h[k] = x;
down(k);
up(k);
}
}
return 0;
}
哈希表
哈希表
存储结构
- 开放地址寻址法
- 拉链法
字符串哈希方式
拉链法O(1)
开数组需要先验一下得到大于100000的最小质数
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100003;
int h[N],e[N],ne[N],idx,k;//拉链法存储
void insert(int x)
{
k = (x%N+N)%N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx++;
}
bool find(int x)
{
k = (x%N+N)%N;
for (int i=h[k];i!=-1;i = ne[i])
{
if (e[i] == x)
return true;
}
return false;
}
int main()
{
int n;
char op[2];
int x;
scanf("%d",&n);
memset(h,-1,sizeof h);
while(n--)
{
scanf("%s%d",op,&x);
if (*op == 'I')
{
insert(x);
}
else
{
if (find(x))
{
cout<<"Yes"<<endl;
}
else cout<<"No"<<endl;
}
}
return 0;
}
开放寻址法
//开放寻址法
#include<iostream>
#include<cstring>
using namespace std;
const int N = 200003,nul = 0x3f3f3f3f;
int h[N];
int find(int x)
{
int t = (x % N + N) % N;
while (h[t]!=nul&& h[t]!=x)
{
t++;
if (t==N) t=0;
}
return t;
}
int main()
{
int n,x;
scanf("%d",&n);
char op[2];
memset(h,0x3f,sizeof h);
while(n--)
{
scanf("%s%d",op,&x);
if (*op=='I')
{
h[find(x)] = x;
}
else{
if (h[find(x)] == nul)
{
cout<<"No"<<endl;
}
else
cout<<"Yes"<<endl;
}
}
return 0;
}
字符串前缀哈希法
1.看成P进制
#include<iostream>
using namespace std;
typedef unsigned long long ULL;
const int N = 100010;
ULL h[N],p[N],P = 131;//p[N]用于存储幂次,P取值为经验值
int get(int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
int m,n;
char str[N];
scanf("%d%d%s",&n,&m,str+1);//从1开始
//初始化h[N],进行p[N]预处理
p[0] = 1;
for (int i=1;i<=n;i++)
{
h[i] = h[i-1]*P+str[i];
p[i] = p[i-1]*P;
}
while(m--)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if (get(l1,r1)==get(l2,r2))
{
puts("Yes");
}
else
puts("No");
}
return 0;
}
stl
dfs
不带剪枝的dfs
排列数字
看成是占位置
#include<iostream>
using namespace std;
const int N = 10;
int n;
int path[N];//用于输出路径
bool st[N];//用于存储当前节点是否被遍历
void dfs(int u)//u是什么?在本题中,u是当前遍历到的层数
{
if(u == n)//如果已经遍历到最后一层,说明一种可能已经产生,需要输出
{
for(int i=0;i<n;i++)
{
cout<<path[i]<<" ";
}
puts("");
return;
}
for (int i=1;i<=n;i++)//从第一层开始遍历
if (!st[i])//如果该节点未被访问
{
path[u] = i;//这一层的这个位置放i
st[i] = true;//将访问状态改为已访问
//该层遍历结束,继续遍历下一层
dfs(u+1);
//遍历完成后回溯
//path[i] = 0;//这里没有也没有关系,因为反正每次遍历都会首先对path进行赋值
st[i] = false;//访问状态要改回来
}
}
int main()
{
cin>>n;
dfs(0);
return 0;
}
n皇后问题
做法1:枚举每一行能否放皇后
#include<iostream>
using namespace std;
const int N = 20;
char g[N][N];
bool col[N],dg[N],udg[N];//col表示行是否被占用,dg表示正对角线是否被占用,udg表示反对角线是否被占用
int n;
void dfs(int u)
{
if (u == n)
{
for (int i=0;i<n;i++){
puts(g[i]);
}
puts("");
return;
}
for (int i=0;i<n;i++){
if (!col[i]&&!dg[u+i]&&!udg[n-u+i])
{
g[u][i] = 'Q';
col[i]=dg[u+i]=udg[n-u+i] = true;
dfs(u+1);
g[u][i] = '.';
col[i]=dg[u+i]=udg[n-u+i] = false;
}
}
}
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
for (int j=0;j<n;j++)
{
g[i][j] = '.';
}
}
dfs(0);
return 0;
}
做法2:枚举每一格
52:13
#include<iostream>
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool row[N],col[N],dg[N],udg[N];
void dfs(int x,int y,int s)//x表示行,y表示列,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;
}
//该格子不放皇后的情况
dfs(x,y+1,s);
//该格子放皇后
if (!col[y]&&!row[x]&&!dg[x+y]&&!udg[x-y+n])
{
g[x][y] = 'Q';
col[y] = row[x]=dg[x+y]=udg[x-y+n] = true;
dfs(x,y+1,++s);
col[y] = row[x]=dg[x+y]=udg[x-y+n] = false;
g[x][y] = '.';
}
}
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
for (int j=0;j<n;j++)
{
g[i][j] = '.';
}
}
dfs(0,0,0);
return 0;
}
bfs
不输出路径
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n,m;//表示输入的地图
int g[N][N];//存储输入地图
int d[N][N];//存储每个点距离原点的距离
typedef pair<int,int> PII;
PII q[N*N];//模拟队列用于存储点对
int bfs()
{
memset(d,-1,sizeof d);//初始化距离数组,-1表示该点没有被拜访过
d[0][0] = 0;//从原点出发
int hh =0,tt=0;//hh队列的头,tt队列的尾
while(hh<=tt)//队列不空
{
PII t = q[hh++];//先取出队头元素,然后将队头元素弹出
int dx[4] = {-1,0,0,1},dy[4] = {0,1,-1,0};//用于模拟四个方向的走位
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&&d[x][y] == -1&&g[x][y] == 0)//点(x,y)在范围内,并且该点未曾被访问,并且该点是可以走的
{
d[x][y] = d[t.first][t.second]+1;//走一格
q[++tt] = {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;
}
输出路径
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n,m;//表示输入的地图
int g[N][N];//存储输入地图
int d[N][N];//存储每个点距离原点的距离
typedef pair<int,int> PII;
PII q[N*N];//模拟队列用于存储点对
PII pre[N][N];//记录前一个点是谁
int bfs()
{
memset(d,-1,sizeof d);//初始化距离数组,-1表示该点没有被拜访过
d[0][0] = 0;//从原点出发
int hh =0,tt=0;//hh队列的头,tt队列的尾
while(hh<=tt)//队列不空
{
PII t = q[hh++];//先取出队头元素,然后将队头元素弹出
int dx[4] = {-1,0,0,1},dy[4] = {0,1,-1,0};//用于模拟四个方向的走位
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&&d[x][y] == -1&&g[x][y] == 0)//点(x,y)在范围内,并且该点未曾被访问,并且该点是可以走的
{
d[x][y] = d[t.first][t.second]+1;//走一格
q[++tt] = {x,y};//将他们加入到队尾
pre[x][y] = t;
}
}
}
int x = n-1,y= m-1;
while(x||y)
{
cout<<x<<" "<<y<<endl;
PII t = pre[x][y];
x = t.first,y=t.second;
}
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;
}
八数码
unordered_map 容器,直译过来就是"无序 map 容器"的意思。所谓“无序”,指的是 unordered_map 容器不会像 map 容器那样对存储的数据进行排序。换句话说,unordered_map 容器和 map 容器仅有一点不同,即 map 容器中存储的数据是有序的,而 unordered_map 容器中是无序的。
具体来讲,unordered_map 容器和 map 容器一样,以键值对(pair类型)的形式存储数据,存储的各个键值对的键互不相同且不允许被修改。但由于 unordered_map 容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。
值得一提的是,unordered_map 容器在头文件中,并位于 std 命名空间中。因此,如果想使用该容器,代码中应包含如下语句:
#include <unordered_map>
using namespace std;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RrflY3zZ-1676292898019)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1654350785831.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xlI08c7l-1676292898019)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1654351055179.png)]
成员方法 | 功能 |
---|---|
begin() | 返回指向容器中第一个键值对的正向迭代器。 |
end() | 返回指向容器中最后一个键值对之后位置的正向迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。 |
empty() | 若容器为空,则返回 true;否则 false。 |
size() | 返回当前容器中存有键值对的个数。 |
max_size() | 返回容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。 |
operator[key] | 该模板类中重载了 [] 运算符,其功能是可以向访问数组中元素那样,只要给定某个键值对的键 key,就可以获取该键对应的值。注意,如果当前容器中没有以 key 为键的键值对,则其会使用该键向当前容器中插入一个新键值对。 |
at(key) | 返回容器中存储的键 key 对应的值,如果 key 不存在,则会抛出 out_of_range 异常。 |
find(key) | 查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。 |
count(key) | 在容器中查找以 key 键的键值对的个数。 |
equal_range(key) | 返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中键为 key 的键值对所在的范围。 |
emplace() | 向容器中添加新键值对,效率比 insert() 方法高。 |
emplace_hint() | 向容器中添加新键值对,效率比 insert() 方法高。 |
insert() | 向容器中添加新键值对。 |
erase() | 删除指定键值对。 |
clear() | 清空容器,即删除容器中存储的所有键值对。 |
swap() | 交换 2 个 unordered_map 容器存储的键值对,前提是必须保证这 2 个容器的类型完全相等。 |
bucket_count() | 返回当前容器底层存储键值对时,使用桶(一个线性链表代表一个桶)的数量。 |
max_bucket_count() | 返回当前系统中,unordered_map 容器底层最多可以使用多少桶。 |
bucket_size(n) | 返回第 n 个桶中存储键值对的数量。 |
bucket(key) | 返回以 key 为键的键值对所在桶的编号。 |
load_factor() | 返回 unordered_map 容器中当前的负载因子。负载因子,指的是的当前容器中存储键值对的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。 |
max_load_factor() | 返回或者设置当前 unordered_map 容器的负载因子。 |
rehash(n) | 将当前容器底层使用桶的数量设置为 n。 |
reserve() | 将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。 |
hash_function() | 返回当前容器使用的哈希函数对象。 |
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<unordered_map>
using namespace std;
int bfs(string start)
{
string end = "12345678x";
queue<string> q;//用于存储一组可行的解
unordered_map<string,int> d;//用键值对的方式存储距离
q.push(start);//初始状态进栈
d[start] = 0;//起始状态距离为0
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};//用于向四个方向走
while(q.size())//当队列不空
{
auto t = q.front();//取出队头元素
q.pop();
int distance = d[t];
if (t == end) return distance;
//状态转换
//遍历t的邻边
int k = t.find("x");//k返回x在字符串中的下标
int x = k/3,y = k%3;//将其转换为在3*3表格中的下标
for (int i=0;i<4;i++)
{
int a = x+dx[i],b=y+dy[i];//在3*3表格中变换位置
if(a>=0&&a<3&&b>=0&&b<3)//a,b均在界内
{
swap(t[k],t[a*3+b]);//在字符串t中交换两个位置对应的字符
if (!d.count(t))//.count方法查找以 key 键的键值对的个数,如果为0,就是这是这个字符串组合第一次被遍历到
{
d[t] = distance+1;
q.push(t);
}
swap(t[k],t[a*3+b]);
}
}
}
return -1;
}
int main()
{
string start;//输入的初始状态将其转换为字符串
for (int i=0;i<9;i++)
{
char c;
cin>>c;
start+=c;
}
cout<<bfs(start)<<endl;
return 0;
}
树的存储
树的重心
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100010;
const int M = 2*N;//无向图需要存储两倍的节点数量
int h[N],e[M],ne[M],idx;
bool st[N];
int ans = N;//最大最小值
int n;
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
//返回以当前u节点为根节点的子树的大小
int dfs(int u)
{
st[u] = true;
int res = 0;//res返回当前遍历过程中最大的连通块大小
int sum = 1;//记录当前遍历过程中连通块的大小(子树的大小)
for (int i=h[u];i!=-1;i=ne[i])
{
int j = e[i];//这里不是很懂,j存储的是节点的值,可是下面又用节点的值作为是否已经遍历过的的标准要是有两个节点值是一样的呢?答:j存储的是树的节点编号!!!
if (!st[j])
{
int s = dfs(j);//返回当前节点为根节点的子树的大小
sum += s;//总的节点数
res = max(res,s);//取子树和当前记录的最大值
}
}
res = max(res,n-sum);
ans = min(ans,res);
return sum;//为进入下一轮循环提供准备
}
int main()
{
memset(h,-1,sizeof h);
cin>>n;
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;
}
图中点的层次
#include<iostream>
#include<cstring>
using namespace std;
const int N =100010;
int h[N],e[N],ne[N],idx;//用邻接表存储,h[N]是表头,e[N]存储节点编号的值,ne[N]存储的是指针,idx表示当前遍历节点
int d[N],q[N];//d[N]用于1号节点到当前节点的距离,因为bfs可以得到最短距离,因此可以用第一次搜到的结果
int n,m;
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int bfs()
{
memset(d,-1,sizeof d);//初始化距离数组
q[0] = 1;//0号节点是编号为1的节点
d[1] = 0;//1号节点是起始节点
int hh=0;
int tt=0;
while(hh<=tt)
{
int t = q[hh++];//取出队头元素
for (int i = h[t];i!=-1;i=ne[i])//遍历其邻边
{
int j = e[i];
if (d[j] == -1)//如果未被遍历
{
d[j]=d[t]+1;
q[++tt] = j;//将该节点加入队列中
}
}
}
return d[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);//对邻接表头节点进行初始化
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);//有向图只要添加一次
}
cout<<bfs()<<endl;//宽度优先搜索适合输出最短路径此类问题
return 0;
}
拓扑排序
对于有向图来说,按照节点指向顺序进行排列。
在拓扑排序中所有的边都是从前指向后
对于一个图,拓扑序列不一定存在,有向无环图拓扑序列一定存在,因此有向无环图又被成为拓扑图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ePG9vGgF-1676292898020)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1654315517833.png)]
有向图:
由定点和有向边组成。
每个有向边是一个形如(u,v)的有序对,其中u和v代表顶点。
当一个有向图包含一个有向边(u,v)时,我们称v临接于u,并且(u,v)表示从u发出,且进入v。
有向无环图:不存在任何一个从某个顶点发出,经过一条或者多条边后,重新又回到出发点的路径。
入度:进入该顶点的边的个数称为该顶点的入度。(可以以任意一个入度为0的顶点作为起始顶点。)
出度:从该顶点发出的边的个数。
任何一个有向无环图中必定至少存在一个入度为0的顶点,至少存在一个出度为0的顶点,否则图中必存在环。
有向无环图对于构造一个任务必须发生在另一个任务之前的这种依赖模型特别有效。有向无环图的另一个应用是规划项目,例如建造房屋时,框架的设计必须在盖屋顶之前。
#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 q[N],d[N];//q[N]用于存储当前符合条件的点,d[N]用于存储入度,入度为零的点可以删除,再删除其出边
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++)
if (!d[i]) q[++tt] = i;//先把入度为零的点加入队列中
//开始常规的bfs
while(hh<=tt)
{
int t = q[hh++];//拿出一个队头遍历其邻边
for(int i=h[t];i!=-1;i=ne[i])
{
int j = e[i];//j存储节点编号
d[j]--;//把t节点指向其邻节点的边全部删除
if (d[j] == 0)//如果删除后出现入度为零的点,入队
{
q[++tt] = j;
}
}
}
return tt==n-1;//如果队列中已经有n个元素说明拓扑排序一次成功
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);//先把邻接表的头节点初始化为-1
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
d[b]++;//如果插入一条从a->b的边,则b的入度增加
}
if(topsort())
{
for (int i=0;i<n;i++) printf("%d ",q[i]);
puts("");
}
else
{
puts("-1");
}
return 0;
}
最短路
n点的数量,m边的数量
-
单源最短路
- 所有边权都是正数
- 朴素Dijikstra算法 O(n^2)
- 堆优化的Dijikstra算法 O(mlogn) 稀疏图
- 存在负权边
- Bellman-Ford O(nm)
- SPFA 一般O(m)最坏O(nm)
- 所有边权都是正数
-
多源汇最短路
-
Floyd算法 O(n^3)、
难点在于建图
dijiksra
-
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510;//注意不能取太大会溢出
int n,m;
int dist[N];//表示从1号点到其他点的最短距离
bool st[N];//表示当前节点是否是最短的那个
int g[N][N];//存储图
int dijkstra()
{
memset(dist,0x3f,sizeof dist);//先进行初始化,距离都为不可达
dist[1] = 0;//到一号节点的距离初始化为0
//遍历
for (int i=0;i<n;i++)
{
int t = -1;//用于存储遍历过程中发现的不在已经确定的最短路集合s中,距离最近的点,存储当前访问的点
for (int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[t]>dist[j]))//如果j点状态尚未确定,并且距离1号点更短
{
t = j;
}
}
st[t] = true;
for (int j=1;j<=n;j++)
{
dist[j] = min(dist[j],dist[t]+g[t][j]);
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);//给图赋初值,0x3f3f3f3f表示正无穷,不可达
for (int i=0;i<m;i++)
{
int x,y,z;
cin>>x>>y>>z;
g[x][y] = min(g[x][y],z);//可能有重边和自环,取最小值
}
int t = dijkstra();
cout<<t<<endl;
return 0;
}
堆优化dijikstra算法
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N =1000010;//因为图很大,实际能用到的点很少,所以用稀疏矩阵存储
//稀疏矩阵用邻接表存储
int h[N],ne[N],e[N],w[N],idx;
bool st[N];
int n,m;
typedef pair<int,int> PII;//前者存储距离,后者存储节点编号
int dist[N];
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
//定义优先队列
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0,1});//进行初始化,1号节点到其自身的距离为0;
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second;//编号
int distance = t.first;//距离
if (st[ver]) continue;//如果当前节点已经有值了,说明已经被处理过了,不需要重复处理
st[ver] = true;
for (int i = h[ver];i!=-1;i = ne[i])//遍历
{
int j = e[i];//记录当前节点的编号
if (dist[j]>distance+w[i])
{
dist[j] = distance+w[i];
heap.push({dist[j],j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);//用邻接表存,多存也没关系
}
int t = dijkstra();
cout<<t<<endl;
return 0;
}
Bellman-Ford算法
处理有负权边的情况
O(nm)
存在负环回路,但最多只能经过k条边
用备份进行更新
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42eAnDBD-1676292898021)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1654416395875.png)]
比如上述,更新3时,使用的是上一次的备份0 正无穷 正无穷 所以不会更新3节点的距离为2,这是因为有边数的限制,所以需要有一个backup数据来记录上一次的情况。
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510,M = 100010;
int dist[N],backup[N];
int n,m,k;
struct Edge{
int a,b,w;
}edges[M];
void bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
for (int i=0;i<k;i++){
memcpy(backup,dist,sizeof dist);
for (int j=0;j<m;j++)
{
int a = edges[j].a;
int b = edges[j].b;
int w = edges[j].w;
dist[b] = min(dist[b],backup[a]+w);
}
}
}
int main()
{
cin>>n>>m>>k;
for (int i=0;i<m;i++)
{
int a,b,w;
cin>>a>>b>>w;
edges[i]={a,b,w};
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f/2) puts("impossible");
else
cout<<dist[n]<<endl;
}
SPFA
Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
- st数组的作用:判断当前的点是否已经加入到队列当中了;已经加入队列的结点就不需要反复的把该点加入到队列中了,就算此次还是会更新到源点的距离,那只用更新一下数值而不用加入到队列当中。
即便不使用st数组最终也没有什么关系,但是使用的好处在于可以提升效率。 - SPFA算法看上去和Dijstra算法长得有一些像但是其中的意义还是相差甚远的:
1] Dijkstra算法中的st数组保存的是当前确定了到源点距离最小的点,且一旦确定了最小那么就不可逆了(不可标记为true后改变为false);SPFA算法中的st数组仅仅只是表示的当前发生过更新的点,且spfa中的st数组可逆(可以在标记为true之后又标记为false)。顺带一提的是BFS中的st数组记录的是当前已经被遍历过的点。
2] Dijkstra算法里使用的是优先队列保存的是当前未确定最小距离的点,目的是快速的取出当前到源点距离最小的点;SPFA算法中使用的是队列(你也可以使用别的数据结构),目的只是记录一下当前发生过更新的点。
-
⭐️Bellman_ford算法里最后return-1的判断条件写的是dist[n]>0x3f3f3f3f/2;而spfa算法写的是dist[n]==0x3f3f3f3f;其原因在于Bellman_ford算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;但是SPFA算法不一样,它相当于采用了BFS,因此遍历到的结点都是与源点连通的,因此如果你要求的n和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f。
-
⭐️ Bellman_ford算法可以存在负权回路,是因为其循环的次数是有限制的因此最终不会发生死循环;但是SPFA算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,因此假如有负权回路请你不要用SPFA否则会死循环。
-
⭐️由于SPFA算法是由Bellman_ford算法优化而来,在最坏的情况下时间复杂度和它一样即时间复杂度为 O(nm)O(nm) ,假如题目时间允许可以直接用SPFA算法去解Dijkstra算法的题目。(好像SPFA有点小小万能的感觉?)
-
⭐️求负环一般使用SPFA算法,方法是用一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n那就证明存在了负环。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fRIHqWLP-1676292898022)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1654423804737.png)]
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N = 100010;
int h[N],e[N],w[N],ne[N],idx;
bool st[N];//存储当前节点是否在队列中
int dist[N];
int n,m;
void add(int a,int b,int c)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
void spfa()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
queue<int> q;//队列里存储所有变小的点
q.push(1);
st[1] = true;//1加入到待更新的队列中
while(q.size())
{
auto t = q.front();
q.pop();
st[t] = false;//从队列中弹出,不在队列中
for (int i=h[t];i!=-1;i = ne[i])
{
int j = e[i];
if (dist[j]>dist[t]+w[i])
{
dist[j] = dist[t]+w[i];//更新数值
if (!st[j])//如果不在队列中
{
st[j] = true;
q.push(j);
}
}
}
}
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
spfa();
if (dist[n] == 0x3f3f3f3f) puts("impossible");
else
cout<<dist[n]<<endl;
}
SPFA统计负环图
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 2010,M= 10010;
int h[N],e[M],ne[M],w[M],idx;
int n,m;
bool st[N];
int dist[N],cnt[N];
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
bool spfa()
{
// 这里不需要初始化dist数组为 正无穷/初始化的原因是, 如果存在负环, 那么dist不管初始化为多少, 都会被更新
queue<int> q;
for (int i=1;i<=n;i++)//编号入栈
{
q.push(i);
st[i] = true;
}
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for (int i=h[t];i!=-1;i=ne[i])
{
int j = e[i];
if (dist[j]>dist[t]+w[i])
{
dist[j] = dist[t]+w[i];
cnt[j] = cnt[t]+1;
if (cnt[j]>=n) return true;//如果大于等于n条边说明有n+1个点,必存在自环
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
if (spfa()) puts("Yes");
else puts("No");
return 0;
}
Floyd算法
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 210,INF = 1e9;
int d[N][N];
int n,m,Q;
void floyd()
{
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
}
int main()
{
cin>>n>>m>>Q;
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++)
{
if (i==j)d[i][j] = 0;
else d[i][j] = INF;
}
}
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
d[x][y] = min(d[x][y],z);
}
floyd();
while(Q--)
{
int x,y;
cin>>x>>y;
if (d[x][y]>INF/2)puts("impossible");
else cout<<d[x][y]<<endl;
}
return 0;
}
最小生成树
朴素Prim
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510,INF = 0x3f3f3f3f;
int g[N][N];//点与边的数量相近,为稠密图,用邻接矩阵存储
int dist[N];//存储当前节点到集合的最短距离
bool st[N];//存储当前节点是否包括在集合中
int n,m;
int prim()
{
memset(dist,0x3f,sizeof dist);
int res = 0;//用于存储最短路径的长度之和
for (int i=0;i<n;i++)
{
int t = -1;
for (int j=1;j<=n;j++)
{
if (!st[j]&&(t==-1||dist[t]>dist[j]))
t = j;
}
if (i&&dist[t] == INF)//如果是第一个点,并且距离是无穷大,说明不可能
return INF;
if (i) res+=dist[t];
for (int j=1;j<=n;j++)
{
dist[j] = min(dist[j],g[t][j]);
}
st[t] = true;
}
return res;
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);//邻接矩阵初始化
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b] = g[b][a] = min(g[a][b],c);
}
int t = prim();
if (t == INF)puts("impossible");
else cout<<t<<endl;
}
二分图
一个图是二分图,当且仅当图中不含有奇数环(环当中边的数量是奇数)
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 100010,M = 200010;
int n,m;
int h[N],e[M],ne[M],idx;
int color[N];//用于染色
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
bool dfs(int u,int c)
{
color[u] = c;
for (int i=h[u];i!=-1;i = ne[i])
{
int j = e[i];
if (!color[j])
{
if(!dfs(j,3-c)) return false;
}
else if(color[j] == c) return false;
}
return true;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);//无向图需要加两次
}
bool flag = true;
for (int i=1;i<=n;i++)
{
if (!color[i])//如果没染色
{
if (!dfs(i,1))
{
flag = false;
break;
}
}
}
if (!flag)cout<<"No"<<endl;
else cout<<"Yes"<<endl;
return 0;
}
匈牙利算法
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510;
const int M = 100010;
int h[N],e[M],ne[M],idx;
bool st[N];//记录是否被遍历过
int match[N];//n2是否被匹配过,match[j]=a,表示女孩j的现有配对男友是a
int n1,n2,m;
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
bool find (int x)
{
for (int i=h[x];i!=-1;i=ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true;
if (match[j] == 0 || find(match[j]))//如果女孩j没有男朋友,或者她原来的男朋友能够预定其它喜欢的女孩。配对成功,更新match
{
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
cin>>n1>>n2>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
int res = 0;
for (int i=1;i<=n1;i++)
{
memset(st,false,sizeof st);//对每个n1来说只遍历每个n2一次
if (find(i)) res++;
}
cout<<res<<endl;
return 0;
}
Kruskal
参考连通块中点的数量(并查集的简单应用)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 200010;
int p[N];
int n,m;
int find(int x)
{
if(p[x]!=x)
{
p[x] = find(p[x]);
}
return p[x];
}
struct Edge{
int a,b,w;
bool operator <(const Edge &W)const
{
return w<W.w;
}
}edge[N];
int main()
{
cin>>n>>m;
for (int i=0;i<m;i++)
{
int a,b,w;
cin>>a>>b>>w;
edge[i] = {a,b,w};
}
int cnt=0;//集合中的边
int res=0;//集合中边的权重
for (int i = 1; i <= n; i ++ ) p[i] = i;
sort( edge, edge+m);
for (int i=0;i<m;i++)
{
int a = edge[i].a,b = edge[i].b,w= edge[i].w;
a = find(a);
b = find(b);
if (a!=b)
{
p[a] = b;
cnt++;
res+=w;
}
}
if (cnt<n-1)cout<<"impossible"<<endl;
else cout<<res<<endl;
return 0;
}
DP
状态表示f(i,j)
- 集合
- 所有选法
- 只考虑前i个物品
- 总体积《=j
- 属性 max min 数量
状态计算
- 集合划分
- 不重
- 不漏
背包问题
0-1背包问题
/*
(1)状态f[i][j]定义:前 i 个物品,背包容量 j 下的最优解(最大价值):
当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 NN 件物品,则需要 NN 次决 策,每一次对第 ii 件物品的决策,状态f[i][j]不断由之前的状态更新而来。
(2)当前背包容量不够(j < v[i]),没得选,因此前 ii 个物品最优解即为前 i−1i−1 个物品最优解:
对应代码:f[i][j] = f[i - 1][j]。
(3)当前背包容量够,可以选,因此需要决策选与不选第 ii 个物品:
选:f[i][j] = f[i - 1][j - v[i]] + w[i]。
不选:f[i][j] = f[i - 1][j] 。
我们的决策是如何取到最大价值,因此以上两种情况取 max() 。
*/
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n,m;
int f[N][N];//第一位表示第i件物品,第二位表示背包容量
int v[N],w[N];//v表示第i个物体的体积,w表示第i个物体的价值
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++) cin>>v[i]>>w[i];
for (int i=1;i<=n;i++)
for (int j=0;j<=m;j++)
{
f[i][j] = f[i-1][j];//不选i的最优解
if (j>=v[i])
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
cout<<f[n][m]<<endl;
return 0;
}
//一维数组版本
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int v[N],w[N];
int n,m;
int f[N];
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
{
cin>>v[i]>>w[i];
}
for (int i=1;i<=n;i++)
for (int j=m;j>=v[i];j--)//注意要逆序,因为如果不逆序的话用的是i层,我们需要用i-1层
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}
完全背包问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int n,m;
int v[N],w[N];
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)cin>>v[i]>>w[i];
for (int i=1;i<=n;i++)
for (int j=0;j<=m;j++)
{
f[i][j] = f[i-1][j];
if (j>=v[i])
f[i][j] = max(f[i-1][j],f[i][j-v[i]]+w[i]);
}
cout<<f[n][m]<<endl;
return 0;
}
//一维的做法
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
for (int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}
多重背包问题
暴力写法
/*
f[i][j]=max(f[i-1][j-v[i]*k]+k*w[i])
*/
//暴力写法
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int n,m;
int v[N],w[N],s[N];
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)cin>>v[i]>>w[i]>>s[i];
for (int i=1;i<=n;i++)
for (int j=0;j<=m;j++)
{
for (int k=0;k*v[i]<=j&k<=s[i];k++)
f[i][j] = max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
}
cout<<f[n][m]<<endl;
return 0;
}
多重背包问题的二进制优化
一个重要的点是,根据二进制和十进制之间的转换,一个十进制数必定可以用二进制数表示出来
那么如果需要的范围不是2倍数,只需要补上差距即可,证明如上,也可见视频动态规划1,1:37
//如果仍用暴力做法,会超时
//二进制优化一个重要的点是任意一个实数可以由二进制数来表示,也就是2^0~2^k其中一项或几项的和。(二进制表示)
//与01背包问题相比,只是多了一个二进制优化打包的过程
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 12010;//n = logN*s
const int M = 2010;
int f[M];
int v[N],w[N];
int n,m;
int main()
{
cin>>n>>m;
int cnt = 0;
for (int i=1;i<=n;i++)
{
int a,b,s;
cin>>a>>b>>s;
int k = 1;
while(k<=s)
{
cnt++;
v[cnt] = a*k;
w[cnt] = b*k;
s -= k;
k *= 2;
}
if (s>0)
{
cnt++;
v[cnt] = a*s;
w[cnt] = b*s;
}
}
n = cnt;
for (int i=1;i<=n;i++)
{
for(int j=m;j>=v[i];j--)
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
分组背包问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int v[N][N],w[N][N],s[N];
int n,m;
int f[N];
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
{
cin>>s[i];
for (int j=0;j<s[i];j++)
{
cin>>v[i][j]>>w[i][j];
}
}
for (int i=1;i<=n;i++)
{
for (int j=m;j>=0;j--)
{
for (int k=0;k<s[i];k++)
{
if (v[i][k]<=j)
{
f[j] = max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
}
}
cout<<f[m]<<endl;
return 0;
}
线性DP
数字三角形
关于下标
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510,INF = 1e9;//INF说明没有存数据
int a[N][N];//存储三角形
int f[N][N];//状态转移
int n;
int main()
{
cin>>n;
//先初始化
for (int i=1;i<=n;i++)
{
for (int j=0;j<=i+1;j++)//因为后一位也会被计算到,要从0开始,两边的边界都要考虑
{
f[i][j] = -INF;
}
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=i;j++)
{
cin>>a[i][j];
}
}
f[1][1] = a[1][1];
for (int i=2;i<=n;i++)
{
for (int j =1;j<=i;j++)
{
f[i][j] = max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
}
}
int res = -INF;
for (int i=1;i<=n;i++)
{
res=max(res,f[n][i]);
}
cout<<res<<endl;
return 0;
}
最长上升子序列
状态表示
状态转移
时间复杂度计算:状态数*转移方程
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int a[N];//存储输入的数
int f[N];//状态方程
int n;
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
cin>>a[i];
for (int i=1;i<=n;i++)
{
f[i] = 1;//最坏的情况只有1(自己)
for (int j=1;j<i;j++)
{
if (a[j]<a[i])
{
f[i] = max(f[i],f[j]+1);
}
}
}
int res = 1;
for (int i=1;i<=n;i++)res = max(res,f[i]);
cout<<res<<endl;
return 0;
}
如果需要输出路径
//输出最长子序列路径
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int a[N];//存储输入的数
int f[N];//状态方程
int g[N];
int n;
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
cin>>a[i];
for (int i=1;i<=n;i++)
{
f[i] = 1;//最坏的情况只有1(自己)
g[i] = 0;
for (int j=1;j<i;j++)
{
if (a[j]<a[i])
{
if (f[i]<f[j]+1)
{
f[i] = f[j]+1;
g[i] = j;
}
}
}
}
int k = 1;//存储位置
for (int i=1;i<=n;i++)
{
if (f[k]<f[i])
k = i;
}
cout<<f[k]<<endl;
for (int i=0,len = f[k];i<=len;i++)
{
cout<<a[k]<<" ";
k = g[k];//类似于链表的处理
}
//cout<<res<<endl;
return 0;
}
最长上升子序列2
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];
int q[N];
int main()
{
cin>>n;
for (int i=0;i<n;i++)cin>>a[i];
q[0] = -2e9;
int len = 0;
for (int i=0;i<n;i++)
{
int l = 0,r = len;
while(l<r)
{
int mid = l+r+1 >>1;
if (q[mid]<a[i]) l=mid;
else r = mid-1;
}
q[r+1] = a[i];
len = max(len,r+1);
}
cout<<len<<endl;
return 0;
}
最长公共子序列
注意f[i-1,j]所包含的范围是大于01这种情况的
f[i-1,j]是指从第一个序列i-1前个字母中出现,从第二个序列的前j个字母中出现的子序列
而01表示的是,不包括i,但是包括b[j]
显然f[i-1,j]不一定包含b[j](但是肯定有情况是包含的)
但是因为我们求的是最大值,所以其实这个东西不影响。但是要知道
此外,f[i-1,j-1]不考虑是因为01和10其实也必然包含这两种情况,所以不必考虑
#include<iostream>
#include<algorithm>
using namespace std;
int n,m;
const int N = 1010;
int f[N][N];
char a[N],b[N];
int main()
{
scanf("%d%d",&n,&m);
scanf("%s%s",a+1,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);
}
}
cout<<f[n][m]<<endl;
return 0;
}
最短编辑距离
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
char a[N],b[N];
int n,m;
int main()
{
cin>>n;
cin>>a+1;
cin>>m;
cin>>b+1;
//初始化
for (int i=0;i<=max(n,m);i++) f[0][i] =f[i][0]= i;
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);
}
}
cout<<f[n][m]<<endl;
return 0;
}
编辑距离
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int N = 15,M = 1010;
char str[M][M];//给定字符串
int f[N][N];
int n,m;
int edit_distance(char a[],char b[])
{
int la = strlen(a+1),lb = strlen(b+1);
for (int i=0;i<=lb;i++) f[0][i] = i;
for (int i=0;i<=la;i++) f[i][0] = i;
for (int i=1;i<=la;i++)
{
for (int j=1;j<=lb;j++)
{
f[i][j] = min(f[i-1][j],f[i][j-1])+1;
f[i][j] = min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]));
}
}
return f[la][lb];
}
int main()
{
cin>>n>>m;
for (int i = 0; i < n; i ++ ) scanf("%s", str[i] + 1);
while(m--)
{
int limit;
int res = 0;
char s[N];
cin>>s+1>>limit;
for (int i=0;i<n;i++)
{
if (edit_distance(str[i],s)<=limit)res++;
//cout<<edit_distance(str[i],s);
}
cout<<res<<endl;
}
return 0;
}
区间DP问题
石子合并
//集合表示将第i堆到第j堆合并的
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 310;
int n;
int s[N];
int f[N][N];
int a[N];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)cin>>s[i];
//求前缀和
for (int i=1;i<=n;i++)s[i] += s[i-1];
//初始化
for (int len = 2;len<=n;len++)
{
for (int i=1;i+len-1<=n;i++)
{
int l = i,r = i+len-1;
f[l][r] = 1e9;
for (int k=l;k<r;k++)
{
f[l][r] = min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
}
}
cout<<f[1][n]<<endl;
return 0;
}
计数类DP
整数划分
方法二:
//完全背包的算法
/*
#include<iostream>
using namespace std;
const int N = 1010;
const int mod = 1e9+7;
int n;
int f[N];
int main()
{
cin>>n;
f[0] = 1;//0的话只有一种方案
for (int i=1;i<=n;i++)
{
for (int j=i;j<=n;j++)
{
f[j] = (f[j]+f[j-i])%mod;
}
}
cout<<f[n]<<endl;
return 0;
}
*/
//计数类dp算法
#include<iostream>
using namespace std;
const int N = 1010;
const int mod = 1e9+7;
int n;
int f[N][N];
int main()
{
cin>>n;
f[0][0] = 1;
for(int i=1;i<=n;i++)
{
for (int j=1;j<=i;j++)
{
f[i][j] = (f[i-1][j-1]+f[i-j][j])%mod;
}
}
int res = 0;
for (int i=0;i<=n;i++)res = (res+f[n][i])%mod;
cout<<res<<endl;
return 0;
}
计数问题
状态压缩DP
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wdABZiuB-1676292898032)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1657958359595.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mw9AlBXe-1676292898032)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\1657958469720.png)]
蒙德里安的梦想
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 12;
const int M = 1<<N;
long long f[N][M];//第一项表示列,第二项表示状态
bool st[M];
int n,m;
int main()
{
while(cin>>n>>m , (n||m))
{
memset(f,0,sizeof f);
//预处理,状态数组存该行下这种摆法是否存在连续奇数个0
for (int i=0;i<1<<n;i++)
{
st[i] = true;
int cnt = 0;
for (int j=0;j<n;j++)
{
//如果是1,进行判断当前0的个数是不是奇数
if (i>>j & 1)
{
if (cnt & 1) st[i] = false;
cnt = 0;
}
else cnt++;
}
if (cnt & 1) st[i] = false;
}
f[0][0] = 1;
for (int i=1;i<=m;i++)
{
for (int j=0;j<1<<n;j++)
{
for (int k=0;k<1<<n;k++)
{
if ((j&k)==0&&st[j|k]) f[i][j] += f[i - 1][k];
}
}
}
cout<<f[m][0]<<endl;
}
return 0;
}
最短Hamilton路径
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 21;
const int M = 1<<N;
int w[N][N];//存储权值
int f[M][N];//f[i][j],i中存储状态,j表示从0~j
int n;
int main()
{
cin>>n;
for (int i=0;i<n;i++)
for (int j=0;j<n;j++)
cin>>w[i][j];//输入权值矩阵
memset(f,0x3f,sizeof f);
f[1][0] = 0;//第一个节点
for (int i=0;i<1<<n;i++)
{
for (int j=0;j<n;j++)
{
for (int k=0;k<n;k++)
{
if (i>>j & 1)//如果访问j
{
if ((i-(1<<j))>>k & 1)
{
f[i][j] = min(f[i][j],f[i-(1<<j)][k]+w[k][j]);
}
}
}
}
}
cout<<f[(1<<n)-1][n-1]<<endl;
return 0;
}
树形DP
没有上司的舞会
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 6010;
bool has_father[N];//存储是不是根
int happy[N];
int f[N][2];//状态值数组
int n;
int e[N],ne[N],h[N],idx;//邻接表用于存储节点
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void dfs(int u)
{
f[u][1] = happy[u];
for (int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
dfs(j);
f[u][0]+=max(f[j][0],f[j][1]);
f[u][1]+=f[j][0];
}
}
int main()
{
cin>>n;
memset(h,-1,sizeof h);
for (int i=1;i<=n;i++)
{
cin>>happy[i];
}
for (int i=0;i<n-1;i++)
{
int a,b;
cin>>a>>b;//b是a的上司
has_father[a] = true;
add(b,a);
}
int root = 1;
while(has_father[root])root++;
dfs(root);
cout<<max(f[root][1],f[root][0]);
return 0;
}
记忆化搜索
滑雪
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 310;
int f[N][N];
int h[N][N];
int n,m;
int dx[4]={-1,1,0,0},dy[4] = {0,0,-1,1};
int dp(int x,int y)
{
int &v = f[x][y];
if (v!=-1) return v;
v = 1;
for (int i=0;i<4;i++)
{
int a,b;
a=x+dx[i],b=y+dy[i];
if(a>=1&&a<=n&&b>=1&&b<=m&&h[a][b]<h[x][y])
{
v = max(v,dp(a,b)+1);
}
}
return v;
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
cin>>h[i][j];
}
}
int res = 0;
memset(f,-1,sizeof f);
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
res = max(res,dp(i,j));
}
}
cout<<res<<endl;
return 0;
}
贪心算法
区间选点
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
struct Range{
int l,r;
bool operator < (const Range &W)
{
return r<W.r;
}
}range[N];//存储区间的结构体
int n;
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
cin>>range[i].l>>range[i].r;
}
sort(range,range+n);//区间排序
int res = 0;//用于统计
int ed = -2e9;//ed是上一个选取点的下标
for (int i=0;i<n;i++)
{
if(range[i].l>ed)
{
res++;
ed = range[i].r;
}
}
cout<<res<<endl;
return 0;
}
最大不相交区间数量
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
struct Range{
int l,r;
bool operator < (const Range &W)
{
return r<W.r;
}
}range[N];//存储区间的结构体
int n;
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
cin>>range[i].l>>range[i].r;
}
sort(range,range+n);//区间排序
int res = 0;//用于统计
int ed = -2e9;//ed是上一个选取点的下标
for (int i=0;i<n;i++)
{
if(range[i].l>ed)
{
res++;
ed = range[i].r;
}
}
cout<<res<<endl;
return 0;
}
区间组数
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range{
int l,r;
bool operator < (const Range &W)const{
return l<W.l;
}
}range[N];
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
cin>>range[i].l>>range[i].r;
}
priority_queue<int,vector<int>,greater<int>> heap;//小根堆
sort(range,range+n);
for (int i=0;i<n;i++)
{
if (heap.empty()||heap.top()>=range[i].l)
{
heap.push(range[i].r);
}
else{
heap.pop();
heap.push(range[i].r);
}
}
cout<<heap.size()<<endl;
return 0;
}
区间覆盖
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range{
int l,r;
bool operator < (const Range &W)
{
return l<W.l;
}
}range[N];//按照区间左端点排序
int main()
{
int st,ed;
cin>>st>>ed;
cin>>n;
for (int i=0;i<n;i++)
{
cin>>range[i].l>>range[i].r;
}
int res = 0;
sort(range,range+n);
bool flag = false;
for (int i=0;i<n;i++)
{
//双指针
int r = -2e9;
int j = i;
while (j < n&&range[j].l<=st)//在所有能覆盖st的区间中选择右端点最大的
{
r = max(r,range[j].r);
j++;
}
if(st>r)
{
flag = false;
break;
}
res++;
if (r>=ed)
{
flag = true;
break;
}
i = j-1;
st = r;
}
if (!flag)cout<<"-1"<<endl;
else
{
cout<<res<<endl;
}
return 0;
}
哈夫曼树
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int n;
cin>>n;
priority_queue<int,vector<int>,greater<int>> heap;//小根堆
while(n--)
{
int x;
cin>>x;
heap.push(x);
}
int res = 0;
while(heap.size()>1)
{
int a = heap.top();
heap.pop();
int b = heap.top();
heap.pop();
res+= a+b;
heap.push(a+b);
}
cout<<res<<endl;
return 0;
}
排序不等式
排队打水
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int t[N];
int n;
int main()
{
int n;
cin>>n;
for (int i=0;i<n;i++)
{
cin>>t[i];
}
sort(t,t+n);
long long res = 0;
for (int i=0;i<n;i++) res+=t[i]*(n-i-1);
cout<<res<<endl;
return 0;
}
绝对值不等式
货仓选址
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int a[N];
int n;
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
cin>>a[i];
}
sort(a,a+n);
long long res = 0;
for (int i=0;i<n;i++)
{
res+=abs(a[i]-a[n/2]);
}
cout<<res<<endl;
return 0;
}
推公式
耍杂技的牛
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 50010;
typedef pair<int,int> PII;
PII cow[N];
int n;
int main()
{
cin>>n;
for (int i=0;i<n;i++)
{
int w,s;
cin>>w>>s;
cow[i] = {w+s,w};
}
sort(cow,cow+n);
int res = -2e9;
int sum = 0;
for (int i=0;i<n;i++)
{
int w = cow[i].second,s = cow[i].first - w;
res = max(res,sum-s);
sum+=w;
}
cout<<res<<endl;
return 0;
}