from AcWing
基础算法
归并排序
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型:
bool check(int x) {}
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
浮点型:
bool check(double x) {}
double bsearch_3(double l, double r){
const double eps = 1e-6;
while (r - l > eps){
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
高精度
vector<int> add(vector<int> &A, vector<int> &B){
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++ ){
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;
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;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
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;
}
vector<int> div(vector<int> &A, int b, int &r)
{
vector<int> C;
r = 0;
for (int i = A.size() - 1; i >= 0; i -- )
{
r = r * 10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
前缀和
一维:
S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]
二维:
S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
差分
一维:
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
二维:
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
位运算
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
尺取法
void solve(){
int res = n+1;
int be=0,ed=0,sum=0;
for( ; ; ){
while(ed<n && sum<s){
sum += a[ed++];
}
if(sum < s) break;
res = min(res,ed-be);
sum -= a[be++];
}
}
三角形边problem
p: 三角形第三条边
i:1~n 读入 num[i]
long long sum = 0;
sort(num+1, num+n+1);
int x, y;
for(int i = 1; i <= n; i++)
{
x = lower_bound(num+i+1, num+n+1, p+num[i]) - (num+i+1);
y = upper_bound(num+i+1, num+n+1, abs(num[i]-p)) - (num+i+1);
sum += x-y;
}
cout << sum;
约瑟夫环
int circle(int n, int m){ int ans; for(int i=2; i <= n; i++){ ans = (ans + m)%i; } return ans+1;}
最长上升子序列(LIS)
const int MAXN;
int a[MAXN]
int low[MAXN];
int main(){
int n;
cin >> n;
memset(low,0x3f,sizeof low);
for(int i=0;i<n;i++)
cin >> a[i];
low[1] = a[1];
int num = 1;
for(int i=1;i<n;i++){
if(a[i] > low[num])
low[++num] = a[i];
else
二分查找
low[ ] = a[i];
}
cout << num;
return 0;
}
离散化
vector<int> alls;
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
int find(int x)
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
}
顺序无所谓的简单离散化
unordered_map<int, int> S;
int get(int x)
{
if (S.count(x) == 0) S[x] = ++ n;
return S[x];
}
数据结构
并查集
(1)朴素并查集:
int p[N];
int find(int x){
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
for (int i = 1; i <= n; i ++ ) p[i] = i;
p[find(a)] = find(b);
(2)维护size的并查集:
int p[N], size[N];
int find(int x){
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
for (int i = 1; i <= n; i ++ ){
p[i] = i;
size[i] = 1;
}
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
(3)维护到祖宗节点距离的并查集:
int p[N], d[N];
int find(int x){
if (p[x] != x){
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
for (int i = 1; i <= n; i ++ ){
p[i] = i;
d[i] = 0;
}
p[find(a)] = find(b);
d[find(a)] = distance;
哈希
(1) 拉链法
int h[N], e[N], ne[N], idx;
void insert(int x){
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++ ;
}
bool find(int x){
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i])
if (e[i] == x)
return true;
return false;
}
(2) 开放寻址法
int h[N];
int find(int x){
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x){
t ++ ;
if (t == N) t = 0;
}
return t;
}
(3) 字符串哈希
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL;
ULL 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;
}
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ ){
while (tt && check(stk[tt], i)) tt--;
stk[ ++ tt] = i;
}
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 ?1
cin >> m;
while(m --)
{
int x;
cin >> x;
while(tt && stk[tt] >= x) tt --;
if(tt) cout << stk[tt] << ' ';
else cout << "-1" << ' ';
stk[ ++ tt] = x;
}
单调队列
确定滑动窗口位于每个位置时,窗口中的最大值和最小值
cin >> n >> k;
for(int i = 0; i < n; i ++) cin >> a[i];
int hh = 0, tt = -1;
for(int i = 0; i < n; i ++){
if(hh <= tt && i - k + 1 > q[hh]) 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 && i - k + 1 > q[hh]) hh ++;
while(hh <= tt && a[q[tt]] <= a[i]) tt --;
q[++ tt] = i;
if(i >= k - 1) cout << a[q[hh]] << ' ';
}
puts("");
Trie树
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 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];
}
对顶堆(动态中位数)
priority_queue<int> down;
priority_queue<int, vector<int>, greater<int>> up;
int x;
scanf("%d", &x);
if (down.empty() || x <= down.top()) down.push(x);
else up.push(x);
if (down.size() > up.size() + 1) up.push(down.top()), down.pop();
if (up.size() > down.size()) down.push(up.top()), up.pop();
哈夫曼树
struct node{
Elem data;
node *lc,*rc;
};
bool operator < (node *a,node *b){
}
priority_queue<*node> pq;
void merge(){
while( pq.size()>1 ){
node *now=newNode();
now->lc=pq.top();
pq.pop();
now->rc=pq.top();
pq.pop();
now->data=calc( now->lc->data , now->rc->data );
pq.push(now);
}
}
priority_queue<int,vector<int>,greater<int> > p;
for(int i=0;i<n;i++){
int x;
cin >> x;
p.push(x);
}
while(1){
int x,y,c;
x = p.top();
p.pop();
y = p.top();
p.pop();
c = (x+y);
sum += c;
p.push(c);
if(p.size()==1) break;
}
STL
vector, 变长数组,倍增的思想
size() 返回元素个数 empty() 返回是否为空 clear() 清空 front()/back()
push_back()/pop_back() begin()/end() [] 支持比较运算,按字典序
pair<int, int>
first, 第一个元素 second, 第二个元素
支持比较运算,以first为第一关键字,以second为第二关键字(字典序)
string,字符串
size()/length() 返回字符串长度 empty() clear()
substr(起始下标,(子串长度)) 返回子串 c_str() 返回字符串所在字符数组的起始地址
queue, 队列
size() empty() push() 向队尾插入一个元素 front() 返回队头元素
back() 返回队尾元素 pop() 弹出队头元素
stack, 栈
size() empty() push() 向栈顶插入一个元素
top() 返回栈顶元素 pop() 弹出栈顶元素
set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
size() empty() clear() begin()/end()
++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset
insert() 插入一个数 find() 查找一个数 count() 返回某一个数的个数
erase()
(1) 输入是一个数x,删除所有x O(k + logn)
(2) 输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
lower_bound(x) 返回大于等于x的最小的数的迭代器
upper_bound(x) 返回大于x的最小的数的迭代器
map/multimap
insert() 插入的数是一个pair
erase() 输入的参数是pair或者迭代器
find()
[] 注意multimap不支持此操作。 时间复杂度是 O(logn)
lower_bound()/upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(), 迭代器的++,--
bitset, 圧位
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]
count() 返回有多少个1 any() 判断是否至少有一个1 none() 判断是否全为0
set() 把所有位置成1 set(k, v) 将第k位变成v reset() 把所有位变成0
flip() 等价于~ flip(k) 把第k位取反
priority_queue, 优先队列,默认是大根堆
size() empty() push() 插入一个元素 top() 返回堆顶元素 pop() 弹出堆顶元素
定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;
plus.
make_heap() 生成一个堆,大顶堆或小顶堆
push_heap() 向堆中插入一个元素,并且使堆的规则依然成立
pop_heap() 是在堆的基础上,弹出堆顶元素
commun use:
for(int i=1;i<=n;i++){
cin >> h[i];
push_heap(h+1,h+1+i,greater<int>());
}
auto cmp = [](const int x, const int y) { return x > y;};
vector<int> nums2 = {40, 50, 10, 30, 20};
make_heap(nums2.begin(), nums2.end(), cmp);
cout << "initial max value : " << nums2.front() << endl;
pop_heap(nums2.begin(), nums2.end(), cmp);
nums.pop_back();
cout << "after pop, the max vsalue : " << nums2.front() << endl;
nums2.push_back(0);
push_heap(nums2.begin(), nums2.end(), cmp);
cout << "after push, the max value : " << nums2.front() << endl;
ps.若要大根堆的形式,则不需要创建 cmp
map 关联容器,提供一对一的hash
key - value
map<int, string> mp;
mp.insert(pair<int, string>(000, "student_zero"));
mp.insert(map<int, string>::value_type(001, "student_one"));
mp[123] = "student_first";
mp[456] = "student_second";
————————————————————————————————————————————————————
iter = mp.find(11);
if(iter != mp.end())
cout<<"Find, the value is" << iter->second << endl;
else cout<<"Do not Find"<<endl;
————————————————————————————————————————————————————
iter = mp.find("123");
mp.erase(iter);
int n = mp.erase("123");
mp.erase(mp.begin(), mp.end());
————————————————————————————————————————————————————
map<int, string>::iterator iter;
map<int, string>::reverse_iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++){
cout<<iter->first<<' '<<iter->second<<endl;
}
int nSize = mp.size();
for(int nindex = 1; nindex <= nSize; nindex++){
cout<<mp[nindex]<<endl;
}
————————————————————————————————————————————————————
iter = mapStudent.lower_bound(1);
cout<<iter->second<<endl;
lower_bound函数用法,这个函数用来返回要查找关键字的下界(是一个迭代器)
upper_bound函数用法,这个函数用来返回要查找关键字的上界(是一个迭代器)
例如:map中已经插入了1,2,3,4的话,如果lower_bound(2),返回2,而upper-bound(2),返回3
————————————————————————————————————————————————————
swap() 交换两个map
set
set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值 自动进行排序
插入删除效率高
————————————————————————————————————————————————————
set<int> s;
set<int>::iterator iter;
for(int i = 1 ; i <= 5; ++i){
s.insert(i);
}
for(iter = s.begin() ; iter != s.end() ; ++iter){
cout<<*iter<<" ";
}
pair<set<int>::const_iterator,set<int>::const_iterator> pr;
pr = s.equal_range(3);
cout<<"第一个大于等于 3 的数是 :"<<*pr.first<<endl;
cout<<"第一个大于 3的数是 : "<<*pr.second<<endl;
————————————————————————————————————————————————————
int a[] = {1,2,3};
set<int> s;
set<int>::iterator iter;
s.insert(a,a+3);
for(iter = s.begin() ; iter != s.end() ; ++iter){
cout<<*iter<<" ";
}
cout<<endl;
pair<set<int>::iterator,bool> pr;
pr = s.insert(5);
if(pr.second){
cout<<*pr.first<<endl;
}
————————————————————————————————————————————————————
set<int> s;
s.insert(1);
s.insert(3);
s.insert(4);
cout<<*s.lower_bound(2)<<endl;
cout<<*s.lower_bound(3)<<endl;
cout<<*s.upper_bound(3)<<endl;
树
近似满二叉树
Elem data[maxn];
int lc(int pos) { return 2*pos; }
int rc(int pos) { return 2*pos+1; }
void preorder(int root){
work(root);
preorder(lc(root));
preorder(rc(root));
}
void postorder(int root){
preorder(lc(root));
preorder(rc(root));
work(root);
}
void inorder(int root){
preorder(lc(root));
work(root);
preorder(rc(root));
}
树的储存与遍历
struct node{
int child[maxn];
int cntchild;
}n[maxn];
void preorder(int root){
work(root);
for(int i=0;i<n[root].cntchild;i++)
preorder(n[root].child[i]);
}
void postorder(int root){
for(int i=0;i<n[root].cntchild;i++)
preorder(n[root].child[i]);
work(root);
}
void inorder(int root){
if(n[root].cntchild)
inorder(n[root].child[0]);
work(root);
for(int i=1;i<n[root].cntchild;i++)
inorder(n[root].child[i]);
}
void bfs(int root){
queue<int> q;
q.push(root);
while( q.size() ){
int now=q.front();
q.pop();
for(int i=0;i<n[now].cntchild;i++)
q.push(n[now].child[i]);
work(now);
}
}
struct node{
int child,bro;
}n[maxn];
void preorder(int root){
work(root);
preorder(n[root].child);
preorder(n[root].bro);
}
void postorder(int root){
postorder(n[root].child);
work(root);
postorder(n[root].bro);
}
vector<int> Edg[maxn];
int pa[maxn];
void addEdge(int u,int v){
Edg[u].push(v);
Edg[v].push(u);
}
void dfs(int root,int fa){
pa[root]=fa;
for(auto to : Edg[root])
if(to!=fa)
dfs(to,root);
work(root);
}
void bfs(int root){
pa[root]=-1;
queue<int> q;
q.push(root);
while( q.size() ){
int now=q.front();
q.pop();
for(auto to : Edg[now])
if(to!=pa[now]){
q.push(to);
pa[to]=now;
}
work(now);
}
}
树的遍历
DLR–前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )
LDR–中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)
LRD–后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)
typedef struct TreeNode
{
int data;
TreeNode * left;
TreeNode * right;
TreeNode * parent;
}TreeNode;
void pre_order(TreeNode * Node)
{
if(Node == NULL)
return;
printf("%d ", Node->data);
pre_order(Node->left);
pre_order(Node->right);
}
void middle_order(TreeNode *Node)
{
if(Node == NULL)
return;
middle_order(Node->left);
printf("%d ", Node->data);
middle_order(Node->right);
}
void post_order(TreeNode *Node)
{
if(Node == NULL)
return;
post_order(Node->left);
post_order(Node->right);
printf("%d ", Node->data);
}
void FloorPrint(pTreeNode Tree)
{
pTreeNode temp[100];
int in = 0;
int out = 0;
temp[in++] = Tree;
while (in > out){
if (temp[out]){
cout << temp[out]->data << " → ";
temp[in++] = temp[out]->leftPtr;
temp[in++] = temp[out]->rightPtr;
}
out++;
}
}
搜索与图论
深度优先遍历
int dfs(int u){
st[u] = true;
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (!st[j]) dfs(j);
}
}
宽度优先遍历
queue<int> q;
st[1] = true;
q.push(1);
while (q.size()){
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i]){
int j = e[i];
if (!st[j]){
st[j] = true;
q.push(j);
}
}
}
拓扑排序
bool topsort(){
int hh = 0, tt = -1;
for (int i = 1; i <= n; i ++ )
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];
if (-- d[j] == 0)
q[ ++ tt] = j;
}
}
return tt == n - 1;
}
int main(){
memset(h,-1,sizeof h);
for(int i=0;i<m;i++){
cin >> a >> b;
add(a,b);
d[b]++;
}
if(!topsort()) puts("-1");
else 输出拓扑排序的数组p[]
}
朴素dijkstra
时间复杂是 O(n2+m), n 表示点数,m 表示边数
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510;
int n,m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
for(int i=0;i<n-1;i++){
int t=-1;
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1 || dist[t]>dist[j]))
t = j;
for(int j=1;j<=n;j++)
dist[j] = min(dist[j],dist[t] + g[t][j]);
st[t]=true;
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(g,0x3f,sizeof g);
while(m--){
int a,b,c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b],c);
}
printf("%d\n",dijkstra());
return 0;
}
堆优化dijkstra
时间复杂度 O(mlogn), n 表示点数,m 表示边数
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int,int> pii;
const int N = 1000010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[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});
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] > dist[ver] + w[i]){
dist[j] = dist[ver] + w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main(){
cin >> n >> m;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
cin >> a >> b >> c;
add(a,b,c);
}
cout << dijkstra() << endl;
return 0;
}
Bellman-Ford算法
时间复杂度 O(nm), n 表示点数,m 表示边数
有边数限制的最短路 边权可能为负数 图中可能 存在负权回路
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510, M = 10010;
struct Edge{
int a,b,c;
}edges[M];
int n,m,k;
int dist[N];
int last[N];
void bellman_ford(){
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
for(int i=0;i<k;i++){
memcpy(last,dist,sizeof dist);
for(int j=0;j<m;j++){
auto e = edges[j];
dist[e.b] = min(dist[e.b],last[e.a]+e.c);
}
}
}
int main(){
cin >> n >> m >> k;
for(int i=0;i<m;i++){
int a,b,c;
cin >> a >> b >> c;
edges[i] = {a,b,c};
}
bellman_ford();
if(dist[n] > 0x3f3f3f3f/2) puts("impossible");
else
cout << dist[n] << endl;
return 0;
}
spfa 算法(队列优化的Bellman-Ford算法)
时间复杂度 平均情况下 O(m),最坏情况下 O(nm) , n 表示点数,m 表示边数 1≤n,m≤105
图中可能存在重边和自环, 边权可能为负数 数据保证不存在负权回路
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c){
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++ ;
}
int spfa(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = 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];
if (!st[j]){
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- ) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
if (t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", t);
return 0;
}
spfa判断图中是否存在负环
时间复杂度是 O(nm), n 表示点数,m 表示边数
可能存在重边和自环, 边权可能为负数。 判断图中是否存在负权回路。
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2010, M = 10010;
int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int a, int b, int c){
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++ ;
}
bool spfa()
{
queue<int> q;
for(int i=1;i<=n;i++){
st[i] = true;
q.push(i);
}
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;
if (!st[j]){
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if(spfa()) cout << "Yes";
else cout << "No";
return 0;
}
在原图的基础上新建一个虚拟源点,从该点向其他所有点连一条权值为0的有向边。那么原图有负环等价于新图有负环。此时在新图上做spfa,将虚拟源点加入队列中。然后进行spfa的第一次迭代,这时会将所有点的距离更新并将所有点插入队列中。
dist[x] 记录虚拟源点到x的最短距离 cnt[x] 记录当前x点到虚拟源点最短路的边数,初始每个点到虚拟源点的距离为0, 只要他能再走n步,即cnt[x] >= n,则表示该图中一定存在负环 由于从虚拟源点到x至少经过n条边时,则说明图中至少有n + 1个点,表示一定有点是重复使用.
若dist[j] > dist[t] + w[i],则表示从t点走到j点能够让权值变少,因此进行对该点j进行更新,并且对应cnt[j] = cnt[t] + 1,往前走一步
注意:该题是判断是否存在负环,并非判断是否存在从1开始的负环,因此需要将所有的点都加入队列中,更新周围的点
floyd算法
时间复杂度是 O(n3), n 表示点数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210, inf = 1e9;
int n,m,Q;
int d[N][N];
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 a,b,c;
cin >> a >> b >> c;
d[a][b] = min(d[a][b],c);
}
floyd();
while(Q--){
int a,b;
cin >> a >> b;
int t = d[a][b];
if(t > inf/2) puts("impossible");
else cout << t << endl;
}
return 0;
}
朴素版prim算法
时间复杂度是 O(n^2 +m), n表示点数,m 表示边数
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510, inf=0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];
bool st[N];
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];
st[t] = true;
for(int j=1;j<=n;j++) dist[j] = min(dist[j],g[t][j]);
}
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;
return 0;
}
kruskal算法
时间复杂度是 O(mlogm), n 表示点数,m 表示边数
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct Edge{
int a, b, w;
bool operator < (const Edge & W) const
{
return w <W.w;
}
}edges[M];
int find(int x){
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal(){
sort(edges, edges + m);
for(int i = 1; i <= n; i ++) p[i] = i;
int res = 0, cnt = 0;
for(int i = 0; i < m; i ++){
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if(a != b){
p[a] = b;
res += w;
cnt ++;
}
}
if(cnt < n - 1) return INF;
else return res;
}
int main(){
cin >> n >>m;
for(int i = 0; i < m; i ++){
int a, b, w;
cin >> a >> b >>w;
edges[i] = {a, b, w};
}
int t = kruskal();
if(t == INF) puts("impossible");
else cout << t <<endl;
}
染色法判别二分图
时间复杂度是 O(n+m), n 表示点数,m 表示边数
#include<iostream>
#include<cstring>
#include<algorithm>
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;
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) puts("Yes");
else puts("No");
return 0;
}
匈牙利算法
时间复杂度是 O(nm), n 表示点数,m 表示边数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100010, M=200010;
int n1, n2;
int h[N], e[M], ne[M], idx;
int match[N];
bool st[N];
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])){
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);
if(find(i)) res++;
}
cout << res;
return 0;
}
常用数论
试除法判定质数
bool is_prime(int x){
if (x < 2) return false;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
试除法分解质因数
void divide(int x){
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0){
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
筛法求素数
int primes[N], cnt;
bool st[N];
void get_primes(int n){
for (int i = 2; i <= n; i ++ ){
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
int primes[N], cnt;
bool st[N];
void get_primes(int n){
for (int i = 2; i <= n; i ++ ){
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ ){
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
试除法求所有约数
vector<int> get_divisors(int x)
{
vector<int> res;
for (int i = 1; i <= x / i; i ++ )
if (x % i == 0)
{
res.push_back(i);
if (i != x / i) res.push_back(x / i);
}
sort(res.begin(), res.end());
return res;
}
欧几里得算法
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
快速幂
求 m^k mod p,时间复杂度 O(logk)。
int qmi(int m, int k, int p){
int res = 1 % p, t = m;
while (k){
if (k&1) res = res * t % p;
t = t * t % p;
k >>= 1;
}
return res;
}
typedef long long ll;
ll qmi(int a,int b,int p){
ll res = 1 % p;
while(b){
if(b & 1) res = res * a%p;
a = a * (ll)a % p;
b >>= 1;
}
return res;
}
|