SDNU huangkj
文章目录
头文件
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define all(s) s.begin(),s.end()
#define NO cout<<"NO"<<endl
#define YES cout<<"YES"<<endl
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<ll> vl;
typedef vector<vector<ll>> vll;
typedef map<int, int> mpii;
typedef map<ll, ll> mpll;
typedef map<char, ll> mpcl;
typedef double lf;
typedef long double Lf;
const int MAXN = 2e5 + 5, MOD = 1e9 + 7, MAXP = 31, inf = 0x3f3f3f3f;
const ll INF = 1e18;
const double eps = 1e-6;
杂项
二分
整数二分
int calc(int x){
int L = 0, R = n + 1;
while(L + 1 < R){
int M = (L + R) / 2;
if(a[M] < x)
L = M;
else
R = M;
}
return L;
}
实数二分
while(R - L < 1e-6){
lf mid = (L + R) >> 1;
if(check(mid)){
r = mid;
}else{
l = mid;
}
}
三分
while(R - L > eps){
mid = (L + R) / 2;
lmid = mid - esp;
rmid = mid + esp;
if(f(lmid) < f(rmid))
r = mid;
else
l = mid;
}
数学
高精度
#include <cstdio>
#include <cstring>
constexpr int LEN = 1004;
int a[LEN], b[LEN], c[LEN], d[LEN];
void clear(int a[]){
for(int i = 0; i < LEN; i++) a[i] = 0;
}
void read(int a[]){
static char s[LEN + 1];
scanf("%s",s);
clear(a);
int len = strlen(s);
for(int i = 0; i < len; i++) a[len - i - 1] = s[i] - '0';
}
void print(int a[]){
int i;
for(i = LEN - 1; i >= 1; i--)
if(a[i] != 0) break;
for(;i >= 0; i--) putchar(a[i] + '0');
putchar('\n');
}
void add(int a[], int b[], int c[]){
clear(c);
for(int i = 0; i < LEN - 1; i++){
c[i] += a[i] + b[i];
if(c[i] >= 10){
c[i + 1] += 1;
c[i] -= 10;
}
}
}
void sub(int a[], int b[], int c[]){
clear(c);
for(int i = 0; i < LEN - 1; i++){
c[i] += a[i] - b[i];
if(c[i] < 0){
c[i + 1] -= 1;
c[i] += 10;
}
}
}
/*
如果a < b,那么就sub(b, a, c)在结果后面加负号.
*/
void mul(int a[], int b[], int c[]){
clear(c);
for(int i = 0; i < LEN -1; i++){
for(int j = 0; j <= i; j++) c[i] += a[j] * b[i - j];
if(c[i] >= 10){
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
}
}
bool greater_eq(int a[], int b[], int last_dg, int len){
if(a[last_dg + len] != 0) return true;
for(int i = len - 1; i >= 0; i--){
if(a[last_dg + i] > b[i]) return true;
if(a[last_dg + i] < b[i]) return false;
}
return true;
}
void div(int a[], int b[], int c[], int d[]){
clear(c);
clear(d);
int la, lb;
for(la = LEN - 1; la > 0; la--)
if(a[la - 1] != 0) break;
for(lb = LEN - 1; lb > 0; lb--)
if(b[lb - 1] != 0) break;
if(lb == 0){
puts("> <");
return;
}
for(int i = 0; i < la; i++) d[i] = a[i];
for(int i = la - lb; i >= 0; i--){
while(greater_eq(d, b, i, lb)){
for(int j = 0; j < lb; j++){
d[i + j] -= b[j];
if(d[i + j] < 0){
d[i + j + 1] -= 1;
d[i + j] += 10;
}
}
c[i] += 1;
}
}
}
int main(){
read(a);
char op[4];
scanf("%s",op);
read(b);
switch(op[0]){
case '+' :
add(a, b, c);
print(c);
break;
case '-' :
sub(a, b, c);
print(c);
break;
case '*' :
mul(a, b, c);
print(c);
break;
case '/' :
div(a, b, c, d);
print(c);
print(d);
break;
default:
puts("> <");
}
return 0;
}
快速幂
实现
ll binpow(ll a, ll b){
if(b == 0) return 1;
ll res =binpow(a, b / 2);
if(b % 2)
return res * res * a;
else
return res * res;
}
ll binpow(ll a, ll b){
ll res = 1;
while(b > 0){
if(b & 1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
第二种实现方法是非递归式的。它在循环的过程中将二进制位为 1 时对应的幂累乘到答案中。尽管两者的理论复杂度是相同的,但第二种在实践过程中的速度是比第一种更快的,因为递归会花费一定的开销。
模意义下取幂
ll binpow(ll a, ll b, ll m){
ll res = 1;
while(b > 0){
if(b & 1) res = res * a %m;
a = a * a % m;
b >>= 1;
}
return res;
}
这是一个非常常见的应用,例如它可以用于计算模意义下的乘法逆元。
既然我们知道取模的运算不会干涉乘法运算,因此我们只需要在计算的过程中取模即可。
数论
素数
实现
bool isPrime(int a){
if(a < 2) return 0;
if(a == 2) return 1;
for(int i = 3;(ll)i * i <= a; i++)//防溢出
if(a % i == 0) return 0;
return 1;
}
最大公约数
实现
欧几里得算法(Educlidean algorithm)
递归写法
//Version 1
int gcd(int a, int b){
if(b == 0) return a;
return gcd(b, a % b);
}
//Version 2
int gcd(int a, int b){ return b == 0 ? a : gcd(b, a % b);}
迭代写法
int gcd(int a, int b){
while(b != 0){
int tmp = a;
a = b;
b =tmp % b;
}
return a;
}
拓展欧几里得算法
扩展欧几里得算法(Extended Euclidean algorithm, EXGCD),常用于求 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)的一组可行解。
int Exgcd(int a, int b, int &x, int &y){
if(!b){
x = 1;
y = 0;
return a;
}
int d = Exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t- (a / b) * y;
return d;
}
函数返回的值为gcd,在这个过程中计算x,y即可。
筛法
素数筛法
欧拉筛
bool isprime[MAXN]; // isprime[i]表示i是不是素数
int prime[MAXN]; // 现在已经筛出的素数列表
int n; // 上限,即筛出<=n的素数
int cnt; // 已经筛出的素数个数
void euler()
{
memset(isprime, true, sizeof(isprime)); // 先全部标记为素数
isprime[1] = false; // 1不是素数
for(int i = 2; i <= n; ++i) // i从2循环到n(外层循环)
{
if(isprime[i]) prime[++cnt] = i;
// 如果i没有被前面的数筛掉,则i是素数
for(int j = 1; j <= cnt && i * prime[j] <= n; ++j)
// 筛掉i的素数倍,即i的prime[j]倍
// j循环枚举现在已经筛出的素数(内层循环)
{
isprime[i * prime[j]] = false;
// 倍数标记为合数,也就是i用prime[j]把i * prime[j]筛掉了
if(i % prime[j] == 0) break;
// 最神奇的一句话,如果i整除prime[j],退出循环
// 这样可以保证线性的时间复杂度
}
}
}
图论
图的存储
直接存边
#include <iostream>
#include <vector>
using namespace std;
struct Edge{
int u, v;
};
int n, m;
vector<Edge> e;
vector<bool> vis;
bool find_edge(int u, int v){
for(int i = 1; i <= m; i++){
if(e[i].u == u && e[i].v == v){
return 1;
}
}
}
void dfs(int u){
if(vis[u]) return;
vis[u] = 1;
for(int i = 1; i <= m; i++){
if(e[i].u == u){
dfs(e[i].v);
}
}
}
int main(){
cin >> n >> m;
vis.resize(n + 1, 0);
e.resize(m + 1);
for(int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v;
return 0;
}
邻接矩阵
#include <iostream>
#include <vector>
using namespace std;
int n, m;
vector<bool> vis;
vector<vector<bool>> adj;
bool find_edge(int u, int v) { return adj[u][b]; }
void dfs(int u){
if(vis[u]) return;
vis[u] = 1;
for(int v = 1; v <= n; v++){
if(adj[u][v]){
dfs(v);
}
}
}
int main(){
cin >> n >> m;
vis.resize(n + 1);
adj.resoze(n + 1, vector<bool>(n + 1));
for(int i = 1 ;i <= m; i++){
int u, v;
cin >> u >> v;
adj[u][v] = 1;
}
return 0;
}
邻接表
#include <iostream>
#include <vector>
using namespace std;
int n, m;
vector<bool> vis;
vector<vector<int>> adj;
bool find_edge(int u, int v){
for(int i = 0; i < adj[u].size(); i++){
if(adj[u][v] == v){
return 1;
}
}
return 0;
}
void dfs(int u){
if(vis[u]) return;
vis[u] = 1;
for(int i = 1; i < adj[u].size(); i++)
dfs(adj[u][v]);
}
int main(){
cin >> n >> m;
vis.resize(n + 1);
adj.resize(n + 1);
for(int i = 1; i <= m; i++){
int u, v;
cin >> u >> v;
adj[u].push_back(v);
}
return 0;
}
链式前向星
核心代码
// head[u] 和 cnt 的初始值都为 -1
void add(int u, int v){
next[++cnt] = head[u]; // 当前边的后继
head[u] = cnt; // 起点 u 的第一条边
to[cnt] = v; // 当前边的终点
}
// 遍历 u 的出边
for(int i = head[u]; ~i; i=nxt[i]){ // ~i 表示 i != -1
int v = to[i];
}
#include <iostream>
#include <vector>
using namespace std;
int n, m;
vector<bool> vis;
vector<int> head, nxt ,to;
void add(int u, int v){
nxt.push_back(head[u]);
head[u] = to.size();
to.push_back(v);
}
bool find_edge(int u, int v){
for(int i = head[u]; ~i; i = nxt[i]){
if(to[i] == v){
return 1;
}
}
return 0;
}
void dfs(int u){
if(vis[u]) return;
vis[u] = 1;
for(int i = head[u]; ~i; i = nxt[i])
dfs(to[i]);
}
int main(){
cin >> n >> m;
vis.resize(n + 1, 0);
head.resize(n + 1, -1);
for(int i = 1; i <= m; i++){
int u, v;
cin >> u >> v;
add(u, v);
}
return 0;
}
DFS
栈实现
vector<vector<int>> adj; // 邻接表
vector<bool> vis; // 记录节点是否已经遍历
void dfs(int s){
stack<int> st;
st.push(s);
vis[s] = 1;
while(!st.empty()){
int u = st.top();
st.pop();
for(int v : adj[u]){
if(!vis[v]){
vis[v] = 1; // 确保栈里无重复元素
st.push(v);
}
}
}
}
递归实现
以邻接表作为图的存储方式:
vector<vector<int>> adj;
vector<bool> vis;
void dfs(const int u){
vis[u] = 1;
for(int v : adj[u])
if(!vis[v])
dfs(v);
}
以链式前向星作为图的存储方式:
void dfs(int u){
vis[u] = 1;
for(int i = head [u]; i; i=e[i].x){
if(!vis[e[i].t]){
dfs(v);
}
}
}
BFS
实现
基于链式前向星的存图方式:
void vfs(int u){
while(!Q.empty())
Q.pop();
vis[u] = 1;
d[u] = 0;
p[u] = -1;
while(!Q.empty()){
u = Q.front();
Q.pop();
for(int i = head[u]; i; i = e[i].nxt){
if(!vis[e[i].to]){
Q.push(e[i].to);
vis[e[i].to] = 1;
d[e[i].to] = d[u] + 1;//节点深度
p[e[i].to] = u;//前节点
}
}
}
}
void restore(int x){
vector<int> res;
for(int v = x; v != -1; v = p[v]){
res.push_back(v);
}
std::reverse(res.begin(),res.end());//逆转容器内元素
for(int i = 0; i < res.size(); i++)
cout << res[i];
}
最短路
Floyd算法(使用任何图,不能有负环)
for(k = 1; k <= n; k++){
for(x = 1; x <= n; x++){
for(y = 1; y <= n; y++){
f[x][y] = min(f[x][y],f[x][k] + f[k][y])
}
}
}
Bellman-Ford算法
struct Edge {
int u, v, w;
};
vector<Edge> edge;
int dis[MAXN], u, v, w;
constexpr int INF = 0x3f3f3f3f;
bool bellmanford(int n, int s){
memset(dis, 0x3f, (n + 1) * sizeof(int));
dis[s] = 0;
bool flag = 0;//判断一轮循环是否发生松弛操作
for(int i = 1; i <= n; i++){
flag = 0;
for(int j = 0; j < edge.size(); j++){
u = edge[j].u, v = edge[j].v, w = edge[j].w;
if(dis[u] == INF) continue;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
flag = 1;
}
}
//没有可以松弛的边时就停止算法
if(!flag){
break;
}
}
// 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环
return flag;
}
Dijkstra算法(非负权边)
struct edge {
int v, w;
};
vector<edge> e[MAXN];
int dis[MAXN], vis[MAXN];
void dijkstra(int n, int s){
memset(dis, 0x3f, (n + 1) * sizeof(int));
dis[s] = 0;
for(int i = 1; i <= n; i++){
int u = 0; mind =0x3f3f3f3f;
for(int j = 1; j <= n; j++)
if(!vis[j] && dis[j] < mind)
u = j, mind = dis[j];
vis[u] = 1;
for(auto ed : e[u]){
int v = ed.v, w=ed,w;
if(dis[v] > dis[u] + w)
dis[v] = dis[u] + w;
}
}
}
拓扑排序
Kahn算法
int n,m;
vector<int> G[MAXN];
int in[MAXN]; // 存储每个结点的入度
bool toposort(){
vector<int> L;
queue<int> S;
for(int i = 1;i <= n;i++)
if(in[i] == 0) S.push(i);
while(!S.empty()){
int u = S.front();
S.pop();
L.push_back(u);
for(auto v : G[u]){
if(--in[v] == 0){
S.push(v);
}
}
}
if(L.size() == n){
for(auto i : L) cout << i << ' ';
return true;
}
return false;
}
数据结构
并查集
普通并查集模板
#include <bits/stdc++.h>
using namespace std;
struct DSU{
vector<int> fa,rk;
int n;
void init(int x){ //一共n个节点
n = x;
rk = fa = vector<int>(n+1); // 开数组下标为0到n
for(int i = 1; i <= n ;++i)fa[i] = i ,rk[i] = 1; //初始化,每个集合只有一个元素,每个元素都是根,每个集合的大小为1
}
int find(int x){return fa[x]==x?x:fa[x] = find(fa[x]);}
/*
int find(int x){
if(x==fa[x]){
return x; // 为根直接返回
}
int root = find(fa[x]);
fa[x] = root; // 这里是路径压缩
return root;
*/
void merge(int u,int v){
int x = find(u),y = find(v); //找到两个集合的根节点
if(x==y)return;//两个元素在同一个集合
if(rk[x]>rk[y])swap(x,y); //按秩合并优化 , 用小的集合连在大的集合下面
fa[x] = y;
rk[y] += rk[x];
}
};
int main(){
int n,m,p;cin>>n>>m>>p;
DSU dsu;dsu.init(n);
for(int u,v;m;m--)cin>>u>>v,dsu.merge(u,v);
for(int u,v;p;p--){
cin>>u>>v;
cout<<(dsu.find(u)==dsu.find(v)?"Yes":"No")<<'\n';
}
return 0;
}
种类并查集
#include "bits/stdc++.h"
using namespace std;
struct DSU{
vector<int> fa,rk;
int n;
void init(int x){ //一共n个节点
n = x;
rk = fa = vector<int>(n+1); // 开数组下标为0到n
for(int i = 1; i <= n ;++i)fa[i] = i ,rk[i] = 1; //初始化,每个集合只有一个元素,每个元素都是根,每个集合的
大小为1
}
int find(int x){return fa[x]==x?x:fa[x] = find(fa[x]);}
void merge(int u,int v){
int x = find(u),y = find(v); //找到两个集合的根节点
if(x==y)return;//两个元素在同一个集合
if(rk[x]>rk[y])swap(x,y); //按秩合并优化 , 用小的集合连在大的集合下面
fa[x] = y;
rk[y] += rk[x];
}
};
int main(){
int n,m;cin>>n>>m;
DSU dsu;dsu.init(2*n);
for(int u,v;m;m--){
char op;cin>>op;
cin>>u>>v;
if(op=='F'){
dsu.merge(u,v); // 和朋友 做朋友
}else{
dsu.merge(u,v+n); // u和 敌人的敌人 做朋友, 并且 v+n 和 u 相连,u也成为了v的敌人
dsu.merge(v,u+n);
}
}
vector<int> vis(2*n+1);
int ans = 0;
for(int i = 1; i <= n ;++i){
int v = dsu.find(i);
if(vis[v]==0){ //每个集合对答案有1的贡献
vis[v]=1;
ans++;
}
}
cout<<ans;
return 0;
}
堆
二叉堆
向上调整/向下调整
void up(int x){
while(x > 1 && h[x] > h[x / 2]){
swap(h[x],h[x / 2]);
x /= 2;
}
}
void down(int x){
while(x * 2 <= n){
t = x * 2;
if(t + 1 <= n && h[t + 1] > h[t]) t++;
if(h[t] <= h[x]) break;
swap(h[x],h[t]);
x = t;
}
}
建堆
//向上调整
//从根开始,按BFS序进行。
void build_heap_1(){
for(i = 1;i <= n;i++) up(i);
}
//向下调整
//从叶子开始
void build_heap_2(){
for(i = n;i >= 1;i--) down(i);
}
对顶堆
priority_queue<int,vector<int>> q1;//大根堆
priority_queue<int,vector<int>,greater<int>> q2;//小根堆
void duidingdui(int n){
int x;
cin >> x;
q1.push(x);
for(int i = 2;i <= n;i++){
cin >> x;
if(x > q1.top()) q2.push(x);
else q1.push(x);
while(abs( (int)q1.size() > (int)q2.size() ) > 1){
if( (int)q1.size() > (int)q2.size() ){
q2.push(q1.top());
q1.pop();
}else{
q1.push(q2.top());
q2.pop();
}
}
}
}