STL
1.vector动态数组
👉1.vector动态数组👈
👉2.stack栈👈
👉3.queue队列👈
👉4.deque双端队列👈
👉5.priority_queue优先队列👈
👉6.map映射👈
👉7.set集合👈
👉8.bitset👈
👉9.pair二元组👈
👉10.tuple元组👈
KMP
#include<bits/stdc++.h>
using namespace std;
string s, t;
int next[1010], l1, l2;
void get(string s){
int j = 0, k = -1;
next[0] = -1;
while(j < l2){
if(k == -1 || t[j] == t[k])
next[++j] = ++k;
else
k = next[k];
}
}
int kmp(string s, string t, int pos){
int j = 0, i = pos;
while(i < l1 && j < l2){
if(j == -1 || s[i] == t[j])
i++, j++;
else
j = next[j];
}
if(j >= l2)
return i - l2 + 1;
else
return -1;
}
int main(){
cin >> s >> t;
l1 = s.size();
l2 = t.size();
cout << kmp(s, t, 0) << endl;
return 0;
}
高精
#include<bits/stdc++.h>
using namespace std;
string s1, s2;
int a[10010], b[10010], c[10010];
void init(int a[], string str1){
int l = str1.size();
for(int i = 1; i <= l; ++i)
a[i] = str1[l - i] - '0';
a[0] = l;
}
void pd(string &str1, string &str2){
if((str1 < str2 && str1.size() == str2.size()) || str1.size() < str2.size()){
swap(str1, str2);
cout << "-";
}
}
int sub(int z[], int x[], int y[]){
int len = x[0];
for(int i = 1; i <= len; i++){
if(x[i] < y[i]){
x[i] += 10;
x[i + 1]--;
}
z[i] = x[i]-y[i];
}
while(len > 1 && z[len] == 0)
len--;
z[0] = len;
}
int add(int z[],int x[],int y[]){
int g = 0;
int len = max(x[0],y[0]);
for(int i = 1; i <= len; i++){
z[i] = x[i] + y[i] + g;
g = z[i] / 10;
z[i] %= 10;
}
if(g > 0)
z[++len] = g;
z[0] = len;
}
void mul(int z[],int x[],int y[]){
int lz = x[0] + y[0] - 1;
for(int i = 1; i <= x[0]; i++){
for(int j = 1; j <= y[0]; j++){
z[i + j - 1] += x[i] * y[j];
z[i + j] += z[i + j - 1] / 10;
z[i + j - 1] %= 10;
}
}
if(z[lz + 1]!=0)
lz++;
while(lz > 1 && z[lz] == 0)
lz--;
z[0] = lz;
}
int main(){
cin >> s1 >> s2;
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
init(a, s1);
init(b, s2);
mul(c, a, b);
for(int i = c[0]; i > 0; i--)
cout << c[i];
return 0;
}
并查集
【算法与数据结构】—— 并查集_酱懵静的博客-CSDN博客_并查集
const int N=1005 //指定并查集所能包含元素的个数(由题意决定)
int pre[N]; //存储每个结点的前驱结点
int rank[N]; //树的高度
void init(int n) //初始化函数,对录入的 n个结点进行初始化
{
for(int i = 0; i < n; i++){
pre[i] = i; //每个结点的上级都是自己
rank[i] = 1; //每个结点构成的树的高度为 1
}
}
int find(int x) //查找结点 x的根结点
{
if(pre[x] == x) return x; //递归出口:x的上级为 x本身,则 x为根结点
return find(pre[x]); //递归查找
}
int find(int x) //改进查找算法:完成路径压缩,将 x的上级直接变为根结点,那么树的高度就会大大降低
{
if(pre[x] == x) return x; //递归出口:x的上级为 x本身,即 x为根结点
return pre[x] = find(pre[x]); //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx
}
bool isSame(int x, int y) //判断两个结点是否连通
{
return find(x) == find(y); //判断两个结点的根结点(即代表元)是否相同
}
bool join(int x,int y)
{
x = find(x); //寻找 x的代表元
y = find(y); //寻找 y的代表元
if(x == y) return false; //如果 x和 y的代表元一致,说明他们共属同一集合,则不需要合并,返回 false,表示合并失败;否则,执行下面的逻辑
if(rank[x] > rank[y]) pre[y]=x; //如果 x的高度大于 y,则令 y的上级为 x
else //否则
{
if(rank[x]==rank[y]) rank[y]++; //如果 x的高度和 y的高度相同,则令 y的高度加1
pre[x]=y; //让 x的上级为 y
}
return true; //返回 true,表示合并成功
}
并查集的主要用途有以下两点:
1、维护无向图的连通性(判断两个点是否在同一连通块内,或增加一条边后是否会产生环);
2、用在求解最小生成树的Kruskal算法里。
Kruskal
#include<bits/stdc++.h> //POJ不支持
#define rep(i,a,n) for (int i=a;i<=n;i++)//i为循环变量,a为初始值,n为界限值,递增
#define per(i,a,n) for (int i=a;i>=n;i--)//i为循环变量, a为初始值,n为界限值,递减。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int inf = 0x3f3f3f3f;//无穷大
const int maxn = 1e5;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
//*******************************分割线,以上为自定义代码模板***************************************//
struct edge{
int s;//边的起始顶点。
int e;//边的终端顶点。
int w;//边权值。
bool operator < (const edge &a){
return w<a.w;
}
};
bool cmp(edge x, edge y){
return x.w < y.w ? true : false;
}
edge temp[maxn];//临时数组存储边。
int verx[maxn];//辅助数组,判断是否连通。
edge tree[maxn];//最小生成树。
int n,m;//n*n的图,m条边。
int cnt;//统计生成结点个数,若不满足n个,则生成失败。
int sum;//最小生成树权值总和。
void print(){
//打印最小生成树函数。
cout<<"最小生成树的权值总和为:"<<sum<<endl;
rep(i,0,cnt-1){
cout<<tree[i].s<<" "<<tree[i].e<<"边权值为"<<tree[i].w<<endl;
}
}
void Kruskal(){
rep(i,1,n)
verx[i]=i;//这里表示各顶点自成一个连通分量。
cnt=0;sum=0;
sort(temp,temp+m, cmp);//将边按权值排列。
int v1,v2;
rep(i,0,m-1){
v1=verx[temp[i].s];
v2=verx[temp[i].e];
if(v1!=v2){
tree[cnt].s=temp[i].s;tree[cnt].e=temp[i].e;tree[cnt].w=temp[i].w;//并入最小生成树。
rep(j,1,n){
//合并v1和v2的两个分量,即两个集合统一编号。
if(verx[j]==v2)verx[j]=v1; //默认集合编号为v2的改为v1.
}
sum+=tree[cnt].w;
cnt++;
}
}
//结束双层for循环之后得到tree即是最小生成树。
print();
}
int main(){
//freopen("in.txt", "r", stdin);//提交的时候要注释掉
IOS;
while(cin>>n>>m){
int u,v,w;
rep(i,0,m-1){
cin>>u>>v>>w;
temp[i].s=u;temp[i].e=v;temp[i].w=w;
}
Kruskal();
}
return 0;
}
djikstra
#include<bits/stdc++.h>
using namespace std;
int ans[100010], head[100010], vis[100010], cnt;
int m, n, s;
struct e{
int to, next, w;
}e[1000010];
struct node{
int num;
int id;
bool operator < (const node &x) const{
return x.num < num;
}
};
void add(int u, int v, int z){
e[++cnt].to = v;
e[cnt].next = head[u];
e[cnt].w = z;
head[u] = cnt;
}
priority_queue <node> q;
int main(){
cin >> n >> m >> s;
for(int i = 1; i <= n; ++i)
ans[i] = 1e9;
ans[s] = 0;
for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
q.push((node) {0, s});
while(!q.empty()){
node t = q.top();
q.pop();
int x = t.id;
if(!vis[x]){
vis[x] = 1;
for(int i = head[x]; i != 0; i = e[i].next){
int v = e[i].to;
if(ans[v] > ans[x] + e[i].w){
ans[v] = ans[x] + e[i].w;
if(!vis[v])
q.push((node) {ans[v], v});
}
}
}
}
for(int i = 1; i <= n; i++)
cout << ans[i] << " ";
return 0;
}
Prim
void prim()
{
dist[1] = 0;//把点1加入集合S,点1在集合S中,将它到集合的距离初始化为0
book[1] = true;//表示点1已经加入到了S集合中
for(int i = 2 ; i <= n ;i++)dist[i] = min(dist[i],g[1][i]);//用点1去更新dist[]
for(int i = 2 ; i <= n ; i++)
{
int temp = INF;//初始化距离
int t = -1;//接下来去寻找离集合S最近的点加入到集合中,用t记录这个点的下标。
for(int j = 2 ; j <= n; j++)
{
if(!book[j]&&dist[j]<temp)//如果这个点没有加入集合S,而且这个点到集合的距离小于temp就将下标赋给t
{
temp = dist[j];//更新集合V到集合S的最小值
t = j;//把点赋给t
}
}
if(t==-1){res = INF ; return ;}
//如果t==-1,意味着在集合V找不到边连向集合S,生成树构建失败,将res赋值正无穷表示构建失败,结束函数
book[t] = true;//如果找到了这个点,就把它加入集合S
res+=dist[t];//加上这个点到集合S的距离
for(int j = 2 ; j <= n ; j++)dist[j] = min(dist[j],g[t][j]);//用新加入的点更新dist[]
}
}
int main()
{
cin>>n>>m;//读入这个图的点数n和边数m
for(int i = 1 ; i<= n ;i++)
{
for(int j = 1; j <= n ;j++)
{
g[i][j] = INF;//初始化任意两个点之间的距离为正无穷(表示这两个点之间没有边)
}
dist[i] = INF;//初始化所有点到集合S的距离都是正无穷
}
for(int i = 1; i <= m ; i++)
{
int a,b,w;
cin>>a>>b>>w;//读入a,b两个点之间的边
g[a][b] = g[b][a] = w;//由于是无向边,我们对g[a][b]和g[b][a]都要赋值
}
prim();//调用prim函数
if(res==INF)//如果res的值是正无穷,表示不能该图不能转化成一棵树,输出orz
cout<<"orz";
else
cout<<res;//否则就输出结果res
return 0;
}
快速幂
int quickPower(int a, int b)//是求a的b次方
{
int ans = 1, base = a;//ans为答案,base为a^(2^n)
while(b > 0)//b是一个变化的二进制数,如果还没有用完
{
if(b & 1)//&是位运算,b&1表示b在二进制下最后一位是不是1,如果是:
ans *= base;//把ans乘上对应的a^(2^n)
base *= base;//base自乘,由a^(2^n)变成a^(2^(n+1))
b >>= 1;//位运算,b右移一位,如101变成10(把最右边的1移掉了),10010变成1001。现在b在二进制下最后一位是刚刚的倒数第二位。结合上面b & 1食用更佳
}
return ans;
}
字符串
字符串截取子串函数
一般调用格式:s.substr(start,len);
s.substr(2,5)为s从2位置开始的5个字符组成的子串。
字符串查找子串函数
一般调用格式:s.find(ss);
找到返回ss在s中的起始位置,否则返回-1。
字符串分割函数
一般调用格式:strtok(s,split);
分解字符串为一组字符串,s为要分解的字符串,split为分隔字符串。
字符串比较函数
一般调用格式:strcmp(str1,str2);
逐个字符比较两个字符串是否相等,相等则返回值为0,不相等返回第一次不同字符的ASCII码的差值。
字符串连接函数
一般调用格式:stract(str1,str2);
将str2指向的字符串连接到str1指向的字符串的后面。函数返回str1的值,即字符串str1的首地址。
字符串统计函数
一般调用格式:count(s.begin(),s.end(),'a');
背包
01背包
for (int i = 1; i <= n; i++)
for (int j = V; j >= 0; j--)
f[j] = max(f[j], f[j - c[i]] + w[i]);
前面的代码中有f o r ( j = V . . . c [ i ] ),还可以将这个循环的下限进行改进。
假设当前物品为i ,即使在后面的循环中i + 1... n 的所有物体都要取,该下限也能保证最优解f [V] 能被更新到,该方法就是避免了这种情况,相当于舍弃了一些无用的状态,减少循环次数,但相应也降低了数组的完整性。f [ j − c [ i ] ]中的j 减去后面所有的c [ i ]才能达到这个bound,所以bound以下的状态是对答案没有贡献的。
for (int i = 1; i <= n; i++) {
int bound = max(V - sum{c[i + 1]...c[n]}, c[i]);
for (int j = V; j >= bound, j--)
f[j] = max(f[j], f[j - c[i]] + w[i]);
}
初始化的细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求"恰好装满背包"时的最优解,有的题目则并没有要求必须把背包装满。这两种问法的区别是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了f [ 0 ]为0 其它f [ 1... V ]均设为− ∞,这样就可以保证最终得到的f [ N ]是一种恰好装满背包的最优解(其实f [ 1... j . . . n ] 分别对应着恰好装满容量为j的背包的最优解)。
如果并没有要求必须把背包装满,而是只希望所装物品价值尽量大,初始化时应该将f [ 0... V ] 全部设为0 (此时f [ j ]则表示不必装满容量为j 的背包的最优解)。
为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是− ∞ 了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0 了。这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。
其实根本区别在于转移。如果用物品根本无法凑出某个容量,设该容量为j ,则f [ j ] 永远不会通过f [ 0 ] = 0 得到正值,因为f数组的转移(变量j 的跳跃)是用V − c [ i ] 完成的,f [ j ] 只会一直在取max中获得负值(加了w [ i ]的负值)。
完全背包
for (int i = 1; i <= n; i++)
for (int j = c[i]; j <= V; j++)
f[j] = max(f[j], f[j - c[i]] + w[i]);
多重背包问题
for (int i = 1; i <= n; i++)
for (int j = V; j >= c[i]; j--)
for (int k = 1; k <= p[i] and k * c[i] <= j; k++)
f[j] = max(f[j], f[j - c[i] * k] + w[i] * k);
for (int i = 1; i <= n; i++) {
int num = min(p[i], V / c[i]); // V/c[i]是最多能放多少个进去,优化上界
for (int k = 1; num > 0; k <<= 1) { //这里的k就相当于上面例子中的1,2,4,6
if (k > num) k = num;
num -= k;
for (int j = V; j >= c[i] * k; j--) // 01背包
f[j] = max(f[j], f[j - c[i] * k] + w[i] * k);
}
}
分组背包问题
问题
有N 件物品和一个容量为V 的背包。第i 件物品的费用是c [ i ] ,价值是w [ i ] 。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
for (int i = 1; i <= n; i++) {
cin >> s; // 第i组的物品数量
for (int j = 1; j <= s; j++) cin >> c[j] >> w[j]; //组中每个物品的属性
for (int j = V; j >= 0; j--)
for (int k = 1; k <= s; k++)
if (j >= c[k])
f[j] = max(f[j], f[j - c[k]] + w[k]);
// 由于每组物品只能选一个,所以可以覆盖之前组内物品最优解的来取最大值
}
记忆化搜索
线段树
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 7;
int n, m, s, t;
int ans;
int a[maxn];
struct segment_tree
{
struct node
{
int l, r;
int num;
}tr[maxn * 4];
void build(int p, int l, int r)
{
tr[p] = {l, r, 0};
if(l == r) {
tr[p].num = a[l];
return ;
}
int mid = l + r >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
}
void modify(int p, int l, int r, int k)
{
if(tr[p].l >= l && tr[p].r <= r) {
tr[p].num += k;
return ;
}
int mid = tr[p].l + tr[p].r >> 1;
if(l <= mid) modify(p << 1, l, r, k);
if(r > mid) modify(p << 1 | 1, l, r, k);
}
void query(int p, int x)
{
ans += tr[p].num;
if(tr[p].l == tr[p].r) return;
int mid = tr[p].l + tr[p].r >> 1;
if(x <= mid) query(p << 1, x);
else query(p << 1 | 1, x);
}
}ST;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++ i) {
scanf("%d", &a[i]);
}
ST.build(1, 1, n);
for (int i = 1; i <= m; ++ i) {
int c;
scanf("%d", &c);
if(c == 1) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
ST.modify(1, x, y, z);
}
else {
ans = 0;
int x;
scanf("%d", &x);
ST.query(1, x);
printf("%d\n", ans);
}
}
return 0;
}
int main()
{
n = 100;
for (int i = 1; i <= n; ++ i) {
a[i] = i;
}
ST.build(1, 1, n);
m = 10;
for (int i = 1; i <= m; ++ i) {
int l = 1, r = 100;
ST.modify(1, l, r, 10000);
ans = 0;
// query(p, x), p = 1, x 为想要查询的点的下标
ST.query(1, 50); // 单点查询 下标为 50 的点的值,ans = 50 + 10000 * i
cout << i << " " << ans << '\n';
ans = 0;
ST.query(1, 100); // 单点查询 ans = 100 + 10000 * i
cout << i << " " << ans << '\n';
}
return 0;
}
二分
模板1:
while (l < r)
{
int mid = l + r >> 1; //(l+r)/2
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
模板2
while (l < r)
{
int mid = l + r + 1 >> 1; //(l+r+1)/2
if (check(mid)) l = mid;
else r = mid - 1;
}
二分查找
#include<iostream>
using namespace std;
const int N=1000010;
int a[N],x,q,n;
int main(){
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
while(q--)
{
cin>>x;
int l=1,r=n; //左右边界
while(l<r) //因为是找第一次出现的位置,那就是尽量往左来,就用模板1
{
int mid=l+r>>1;
if(a[mid]>=x) r=mid; //判断条件,如果值大于等于目标值,说明在目标值右边,尽量往左来
else l=mid+1;
}
if(a[l]!=x){ //如果找不到这个值
cout<<-1<<" ";
continue;
}
cout<<l<<" ";
}
return 0;
}
什么是二分答案?
答案属于一个区间,当这个区间很大时,暴力超时。但重要的是——这个区间是对题目中的某个量有单调性的,此时,我们就会二分答案。每一次二分会做一次判断,看是否对应的那个量达到了需要的大小。
判断:根据题意写个check函数,如果满足check,就放弃右半区间(或左半区间),如果不满足,就放弃左半区间(或右半区间)。一直往复,直至到最终的答案。
如何判断一个题是不是用二分答案做的呢?
树状数组
倍增
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<math.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=500007;
const ll INF=1e9+9;
const ll mod=2147483647;
const double EPS=1e-10;//-10次方约等于趋近为0
const double Pi=3.1415926535897;
ll n,m;
struct node
{
ll u,v,nex;
}e[N<<1];
ll head[N],cnt;
void add(ll u,ll v)
{
e[++cnt].v=v;
e[cnt].u=u;//没什么用,还白占空间
e[cnt].nex=head[u];
head[u]=cnt;
}
ll depth[N],fa[N][30],lg[N],s,x,y;
/*dfs函数的作用就是更新该点的所有祖先的fa数组,并通过递归把
该节点的所有的子节点和该节点一样去更新*/
void dfs(ll now,ll fath)//子节点和父节点
{
fa[now][0]=fath;//更新一下fa数组,2^0=1就是父节点
depth[now]=depth[fath]+1;//更新深度
over(i,1,lg[depth[now]]-1)
fa[now][i]=fa[fa[now][i-1]][i-1];
/*更新now的所有 2^i 级的祖先。先找到now的2^(i-1)级祖先,再往上找
该祖先的2^(i-1)级祖先,就是now的2^i祖先,必须一节一节地往上搜*/
for(ll i=head[now];i;i=e[i].nex)//链式前向星遍历
//如果now有子节点的话,就递归往子节点的子节点走(禁止套娃)
if(e[i].v!=fath)
dfs(e[i].v,now);
}
inline ll LCA(ll x,ll y)
{
if(depth[x]<depth[y])//用数学语言就是说不妨设x的深度比y的深度大
swap(x,y);//这样下面只需要写一种代码就好了
while(depth[x]>depth[y])
//让x跳到y的高度(同一高度)
x=fa[x][lg[depth[x]-depth[y]]-1];
//如果跳到一块了那LCA肯定就是y了
if(x==y)
return x;
for(ll k=lg[depth[x]]-1;k>=0;--k)//倒着从大到小地跳
/*因为我们要求跳到x和y的LCA的下一层,所以没有跳到的时候就
让x和y利用dfs里早就用倍增算法处理过的祖先路径快速地一块往上跳*/
if(fa[x][k]!=fa[y][k])
x=fa[x][k],y=fa[y][k];//往上跳
return fa[x][0];//返回x,y的父节点(肯定是相同的嘛)
}
int main()
{
scanf("%lld%lld%lld",&n,&m,&s);
over(i,1,n-1)
{
scanf("%lld%lld",&x,&y);
add(x,y),add(y,x);//无向图一定要记得建双向边
}
over(i,1,n)//预处理一下
lg[i]=lg[i-1]+(1<<lg[i-1]==i);//log2(8)=3//这个手写的lg[]要-1才能用lg[8]=4;
dfs(s,0);//从树根开始,因为用的是链式前向星所以给一个假想根0(其实就是到这儿停)
//dfs一下,预处理各点的深度和祖先
over(i,1,m)
{
scanf("%lld%lld",&x,&y);
printf("%lld\n",LCA(x,y));
}
return 0;
}
欧几里得
int gcd(int a, int b){
if(b == 0)
return a;
else
return gcd(b, a % b);
}
快读快写
int read() {
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
void write(int x) {
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
time
stop = clock();
double k = (double)(stop - start) / CLOCKS_PER_SEC;
std::cout << "time: "<< k << "s" <<std::endl;
rand
#include<bits/stdc++.h>
using namespace std;
int main(){
srand(time(0);
int k = rand() % 2;
cout << k << endl;
return 0;
}