待完成:拓扑排序(还有其他的想不起来了)
快读
inline int rd() {
char ch = getchar(); int x = 0, f = 1;
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return f * x;
}
快速乘法
ll fast_mul(ll a, ll b, ll mod) {
ll res = 0;
while (b > 0) {
if (b & 1) {
res = (res + a) % mod;
}
a = (a + a) % mod;
b >>= 1;
}
return res;
}
快速幂
ll fast_pow(ll a, ll b, ll mod) {
ll res = 1;
while (b > 0) {
if(b & 1) {
res = res * a % mod;
// res = fast_mul(res, a);
}
a = a * a % mod;
// a = fast_mul(a, a);
b >>= 1;
}
return res;
}
二分法求平方根
const double eps = 1e-7;
double get_sqrt(double k) {
double l = 0, r = k;
while (r - l > eps) {
double mid = (l + r) / 2;
if (mid * mid <= k) {
l = mid;
}
else {
r = mid;
}
}
return l;
}
int main() {
double n;
cin >> n;
double ans = get_sqrt(n);
printf("%.5lf
", ans);
return 0;
}
二分图判定
染色法:如果当前点未被染色,则从此点开始进行 DFS 并进行染色,对路径上的点交替染色。 如果发现在某个点时矛盾,原图就不为二分图。
int n, m; // n 表示点数, m 表示边数
int ans[N]; // 标记每个点被染的颜色
bool dfs(int u, int color) {
ans[u] = color;
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (!ans[v]) {
if (!dfs(v, 1 - color)) {
return false;
}
} else if (ans[v] == color) {
return false;
}
}
return true;
}
bool judge_bipartite(int n) {
for (int i = 0; i < n; i++) {
if (!ans[i]) {
if (!dfs(i, 0)) {
return false;
}
}
}
return true;
}
并查集
int fa[N], n;
void init() {
for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) {
if (fa[x] == x) return fa[x];
return fa[x] = find(fa[x]);
}
void unite(int x, int y) {
x = find(x);
y = find(y);
if (x != y) f[x] = y;
}
树的重心
int n;
int minNode = -1, minBalance = MAX_N;
int dfs(int u, int pre) {
int sz = 0, maxSubtree = 0;
for (int i = p[u]; i != -1; i = E[i].next) {
if (E[i].v != pre) {
int son = dfs(E[i].v, u);
sz += son;
maxSubtree = max(maxSubtree, son);
}
}
sz++;
maxSubtree = max(maxSubtree, n - sz);
if (maxSubtree < minBalance) {
minBalance = maxSubtree;
minNode = u;
}
return sz;
}
int main() {
cin >> n;
init();
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs(1, 0);
cout << minNode << endl;
return 0;
}
树的直径
const int MAX_N = 100;
const int MAX_M = 10000;
struct Edge {
int v, next;
int len;
} E[MAX_M];
int p[MAX_N], eid;
void init() {
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v) {
E[eid].v = v;
E[eid].next = p[u];
p[u] = eid++;
}
int maxlen,point;
void dfs(int u,int pre,int step) {
if(step>maxlen) {
maxlen=step;
point=u;
}
for(int i=p[u];i!=-1;i=E[i].next) {
if(E[i].v!=pre) {
dfs(E[i].v,u,step+1);
}
}
}
int diameter() {
maxlen=-1;
dfs(1,0,0);
maxlen=-1;
dfs(point,0,0);
return maxlen;
}
int main() {
int n;
cin >> n;
init();
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
insert(u, v);
insert(v, u);
}
cout << diameter() << endl;
return 0;
}
二叉树遍历
const int N = 1e3 + 10;
int son[N][2], root;
vector<int> v1, v2, v3, v4;
void build() {
root = 7;
son[7][0] = 1;
son[7][1] = 6;
son[1][1] = 4;
son[4][0] = 3;
son[4][1] = 2;
son[6][0] = 5;
}
void print() {
cout << "preorder:";
for (int i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << endl;
cout << "inorder:";
for (int i = 0; i < v2.size(); i++) {
cout << v2[i] << " ";
}
cout << endl;
cout << "postorder:";
for (int i = 0; i < v3.size(); i++) {
cout << v3[i] << " ";
}
cout << endl;
cout << "levelorder:";
for (int i = 0; i < v4.size(); i++) {
cout << v4[i] << " ";
}
cout << endl;
}
// 先序遍历:根左右
void preorder(int u) {
if (!u) return ;
v1.push_back(u);
preorder(son[u][0]);
preorder(son[u][1]);
}
// 中序遍历:左根右
void inorder(int u) {
if (!u) return ;
inorder(son[u][0]);
v2.push_back(u);
inorder(son[u][1]);
}
// 后序遍历:左右根
void postorder(int u) {
if (!u) return ;
postorder(son[u][0]);
postorder(son[u][1]);
v3.push_back(u);
}
// 层序遍历
void levelorder() {
queue<int> q;
q.push(root);
while (!q.empty()) {
int u = q.front();
q.pop();
v4.push_back(u);
if (son[u][0]) q.push(son[u][0]);
if (son[u][1]) q.push(son[u][1]);
}
}
int main() {
build();
preorder(root);
inorder(root);
postorder(root);
levelorder();
print();
return 0;
}
确定二叉树
根据先序遍历和中序遍历确定二叉树
int n, a[N], b[N], son[N][2];
// a数组为中序遍历,b数组为先序遍历
// x1、y1为当前子树在a数组中下标的范围,x2为前子树在b数组中下标的起点
void dfs(int x1, int y1, int x2) {
int pos = x1;
while (a[pos] != b[x2]) { //在中序遍历里找到当前子树的根
pos++;
}
int len1 = pos - x1, len2 = y1 - pos; //在中序遍历里确定当前子树的左子树和右子树大小
if (len1) { //如果有左子树,那么根据先序遍历确定这个节点的左孩子
son[b[x2]][0] = b[x2 + 1];
dfs(x1, pos - 1, x2 + 1); //递归进入左子树
}
if (len2) { //如果有右子树,那么根据先序遍历确定这个节点的右孩子
son[b[x2]][1] = b[x2 + 1 + len1];
dfs(pos + 1, y1, x2 + 1 + len1); //递归进入右子树
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
dfs(1, n, 1);
for (int i = 1; i <= n; i++) {
cout << son[i][0] << " " << son[i][1] << endl;
}
return 0;
}
堆的实现
/** 插入操作: 向堆中插入一个新元素;在数组的最末尾插入新结点。
* 然后自下而上调整子结点与父结点:
* 比较当前结点与父结点,不满足堆性质则交换,使得当前子树满足二叉堆的性质
* 时间复杂度:O(log n)
**/
void push(int a[], int i, int& n) { // i为插入的值,n为插入之前堆得大小
n++; // 调整大小
a[n] = i; // 放进堆得最后
int p = n;
while (p > 1 && a[p / 2] > a[p]) { // 调整,如果不满足堆得性质,交换父节点和当前节点
swap(a[p / 2], a[p]);
p /= 2;
}
}
/**
* 弹出操作:删除堆顶元素,再把堆存储的最后那个结点填在根结点处。再从上而下调整父结点与它的子节点。
* 时间复杂度:O(log n)
**/
int pop(int a[], int &n) {
int res = a[1]; // 记录堆顶元素
a[1] = a[n]; // 把堆顶元素换到最后
n--; // 调整大小,此时最后一位虽然有值但不再次使用
int p = 1, t;
while (p * 2 <= n) { //调整
if (p * 2 + 1 > n || a[p * 2 + 1] >= a[p * 2]) { // 找到左右两个儿子较小者或没有友儿子
t = p * 2;
}
else {
t = p * 2 + 1;
}
if (a[p] > a[t]) { // 如果不满足堆得性质
swap(a[p], a[t]);
p = t;
}
else break; // 否则调整完成
}
return res;
}
/**
* 删除操作:使该元素与堆尾元素交换,调整堆容量,再由原堆尾元素的当前位置自顶向下调整。
* 时间复杂度:O(log n)
* 与弹出操作类似
**/
归并排序
int n, a[N], t[N];
void merge_sort(int x, int y) {
if (y - x > 1) {
int m = (x + y) / 2;
merge_sort(x, m);
merge_sort(m, y);
int p = x, q = m, i = x;
while (p < m || q < y) {
if (q >= y || p < m && a[p] <= a[q]) t[i++] = a[p++];
else t[i++] = a[q++];
}
for (int i = x; i < y; i++) a[i] = t[i];
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
merge_sort(0, n); // 左闭右开
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << "
";
return 0;
}
矩阵乘法
struct matrix {
int a[100][100];
int n, m;
};
matrix matrix_mul(matrix A, matrix B) {
matrix ret;
ret.n = A.n;
ret.m = B.m;
memset(ret.a, 0, sizeof(ret.a));
for (int i = 0; i < ret.n; i++) {
for (int j = 0; j < ret.m; j++) {
for (int k = 0; k < A.m; k++) {
ret.a[i][j] += A.a[i][k] * B.a[k][j];
}
}
}
return ret;
}
int main() {
matrix A, B;
cin >> A.n >> A.m;
for (int i = 0; i < A.n; i++) {
for (int j = 0; j < A.m; j++) {
cin >> A.a[i][j];
}
}
cin >> B.n >> B.m;
for (int i = 0; i < B.n; i++) {
for (int j = 0; j < B.m; j++) {
cin >> B.a[i][j];
}
}
if (A.m != B.n) {
cout << "No" << endl;
}
else {
matrix C = matrix_mul(A, B);
for (int i = 0; i < C.n; i++) {
for (int j = 0; j < C.m; j++) {
cout << C.a[i][j] << " ";
}
cout << endl;
}
}
return 0;
}
/*
简单写法
#include <bits/stdc++.h>
using namespace std;
int n, m, k, a[107][107], b[107][107], c[107][107];
int main() {
scanf("%d%d%d", &n, &m, &k)
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
for(int i = 1; i <= m; ++i) for(int j = 1; j <= k; ++j) scanf("%d", &b[i][j]);
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) for(int l = 1; l <= k; ++l) c[i][j] += a[i][l] * b[l][j];
for(int i = 1; i <= n; ++i) {for(int j = 1; j <= k; ++j) printf("%d", c[i][j]); puts("");}
return 0;
}
*/
矩阵快速幂
const int Mod = 1e9 + 7;
struct matrix {
int a[N][N];
int n;
};
matrix matrix_mul(matrix A, matrix B) {
matrix ret;
ret.n = A.n;
memset(ret.a, 0, sizeof(ret.a));
for (int i = 0; i < ret.n; i++) {
for (int j = 0; j < ret.n; j++) {
for (int k = 0; k < A.n; k++) {
ret.a[i][j] = (ret.a[i][j] + A.a[i][k] * B.a[k][j] % Mod) % Mod;
}
}
}
return ret;
}
matrix unit(int n) {
matrix ret;
ret.n = n;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) {
ret.a[i][j] = 1;
} else {
ret.a[i][j] = 0;
}
}
}
return ret;
}
matrix matrix_pow(matrix A, int p) {
matrix res = unit(A.n);
while (p > 0) {
if (p & 1) {
res = matrix_mul(res, A);
}
A = matrix_mul(A, A);
p >>= 1;
}
return res;
}
int main() {
matrix A;
int p;
cin >> A.n >> p;
for (int i = 0; i < A.n; i++) {
for (int j = 0; j < A.n; j++) {
cin >> A.a[i][j];
}
}
matrix C = matrix_pow(A, p);
for (int i = 0; i < C.n; i++) {
for (int j = 0; j < C.n; j++) {
cout << C.a[i][j] << " ";
}
cout << endl;
}
return 0;
}
矩阵快速幂求斐波那契数列
typedef long long ll;
const ll N = 110;
ll Mod;
struct matrix {
ll a[N][N];
ll n, m;
};
matrix matrix_mul(matrix A, matrix B) {
matrix ret;
ret.n = A.n; ret.m = B.m;
memset(ret.a, 0, sizeof(ret.a));
for (ll i = 0; i < ret.n; i++) {
for (ll j = 0; j < ret.m; j++) {
for (ll k = 0; k < A.m; k++) {
ret.a[i][j] = (ret.a[i][j] + A.a[i][k] * B.a[k][j] % Mod) % Mod;
}
}
}
return ret;
}
matrix unit(ll n, ll m) {
matrix ret;
ret.n = n; ret.m = m;
for (ll i = 0; i < n; i++) {
for (ll j = 0; j < m; j++) {
if (i == j) {
ret.a[i][j] = 1;
} else {
ret.a[i][j] = 0;
}
}
}
return ret;
}
matrix matrix_pow(matrix A, ll p) {
matrix res = unit(A.n, A.m);
while (p > 0) {
if (p & 1) {
res = matrix_mul(res, A);
}
A = matrix_mul(A, A);
p >>= 1;
}
return res;
}
int main() {
matrix A;
ll n;
cin >> n >> Mod;
A.n = A.m = 2;
for (ll i = 0; i < 2; i++) {
for (ll j = 0; j < 2; j++) {
A.a[i][j] = 1;
}
}
A.a[0][0] = 0;
matrix B;
B.n = 1; B.m = 2;
B.a[0][0] = 1; B.a[0][1] = 1;
matrix C = matrix_mul(B, matrix_pow(A, n - 1));
cout << C.a[0][0] << endl;
return 0;
}
( ext{Bellman-Ford})
时间复杂度:(O(VE))
int E,V;
int d[N];
struct edge{
int from;
int to;
int cost;
}es[N];
void bellman_ford(int s) {
for(int i=0;i<V;i++) d[i]=INF;
d[s]=0;
while(true) {
bool update=false;
for(int i=0;i<E;i++) {
edge e=es[i];
if(d[e.from]!=INF && d[e.to]>d[e.from]+e.cost) {
d[e.to]=d[e.from]+e.cost;
update=true;
// 判断负环需要cnt数组,加到n时代表有负环
}
}
if(!update) break;
}
}
int main() {
int s;
cin>>E>>V>>s;
s--;
for(int i=0;i<E;i++) {
int from,to;
cin>>from>>to;
from--;to--;
es[i].from=from;
es[i].to=to;
cin>>es[i].cost;
}
bellman_ford(s);
for(int i=0;i<V;i++) cout<<d[i]<<"
";
return 0;
}
( ext{SPFA})
最好时间复杂度:(O(kE)) ,其中 (k) 为常数。
最坏时间复杂度:(O(VE))
struct node
{
int v, w;
node(int vv, int ww)
{
v = vv;
w = ww;
}
};
vector<node> g[N];
int n, m, s;
int d[N];
bool in_queue[N];
queue<int> q;
int main()
{
cin >> n >> m >> s;
for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
g[u].push_back(node(v, w));
g[v].push_back(node(u, w));
}
memset(d, 0x3f, sizeof(d));
d[s] = 0;
in_queue[s] = true;
q.push(s);
while (!q.empty())
{
int v = q.front();
q.pop();
in_queue[v] = false;
for (int i = 0; i < g[v].size(); i++)
{
int x = g[v][i].v;
if (d[x] > d[v] + g[v][i].w)
{
d[x] = d[v] + g[v][i].w;
if (!in_queue[x])
{
q.push(x);
in_queue[x] = true;
}
// 判断负环同bellman-ford
}
}
}
for (int i = 1; i <= n; i++)
{
cout << d[i] << " ";
}
return 0;
}
( ext{Floyd})
时间复杂度: (O(n^3))
void floyd() {
for(int k=1;k<=n;k++) {
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
}
}
}
}
( ext{Dijkstra})
无堆优化时间复杂度: (O(V^2))
堆优化时间复杂度: (O((V + E) log V))
typedef pair<int,int> P;
struct edge{
int to,cost;
};
int n,m;
vector<edge> g[N];
int d[N];
void dijkstra(int s) {
priority_queue<P,vector<P>,greater<P> > que;
for(int i=0;i<n;i++) d[i]=INT_MAX;
d[s]=0;
que.push(P(0,s));
while(!que.empty()) {
int mind=que.top().first;
int v=que.top().second;
que.pop();
if(d[v]!=mind) continue;
for(int i=0;i<g[v].size();i++) {
edge e=g[v][i];
if(d[e.to]>d[v]+e.cost) {
d[e.to]=d[v]+e.cost;
que.push(P(d[e.to],e.to));
}
}
}
}
int main() {
int s;
cin>>n>>m>>s;
s--;
for(int i=0;i<m;i++) {
int from;
edge e;
cin>>from>>e.to>>e.cost;
from--;e.to--;
g[from].push_back(e);
}
dijkstra(s);
for(int i=0;i<n;i++) cout<<d[i]<<" ";
cout<<"
";
return 0;
}
( ext{Kruskal})
int n, m;
struct Edge {
int u, v;
int len;
} E[MAX_M];
bool cmp (Edge a, Edge b) {
return a.len < b.len;
}
int fa[MAX_N];
void init() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int find(int x) {
if (fa[x] == x) {
return x;
}
return fa[x] = find(fa[x]);
}
int kruskal(int n,int m) {
int sum=0;
init();
sort(E,E+m,cmp);
for(int i=0;i<m;i++) {
int fu=find(E[i].u);
int fv=find(E[i].v);
if(fu!=fv) {
fa[fv]=fu;
sum+=E[i].len;
}
}
return sum;
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> E[i].u >> E[i].v >> E[i].len;
}
cout << kruskal(n, m) << endl;
return 0;
}
( ext{ST})算法
RMQ问题,求区间最值。
时间复杂度:
- 预处理: (O(nlog n))
- 单次询问: (O(1))
const int M = 25;
int n, f[N][M], a[N];
void init() {
for (int i = 1; i <= n; i++)
f[i][0] = a[i];
for (int j = 1; (1 << j) <= n; j++) // 也可以到20
for (int i = 1; i + (1 << j) - 1 <= n; i++) // 和LCA加以区分
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); // 最大值
}
int query(int l, int r) {
int i = log2(r - l + 1);
return max(f[l][i], f[r - (1 << i) + 1][i]);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
init();
int Q;
cin >> Q;
while (Q--) {
int l, r;
cin >> l >> r;
cout << query(l, r) << endl;
}
return 0;
}
( ext{LCA})
最近公共祖先问题,倍增算法
时间复杂度:
-
预处理:(O(nlog n))
-
单词查询:(O(log n))
注意代码12行
const int M = 20;
int f[N][M], n, d[N];
vector<int> G[N];
void init() {
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i <= n; i++) { // 注意!i到n
f[i][j] = f[f[i][j - 1]][j - 1];
}
}
}
void dfs(int u) { // 求深度,求每个节点的父亲
d[u] = d[f[u][0]] + 1;
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (v != f[u][0]) {
f[v][0] = u;
dfs(v);
}
}
}
int lca(int x, int y) {
if (d[x] < d[y]) swap(x, y); // 让 x 成为较深的一点
int K = 0;
while ((1 << (K + 1)) <= d[x]) K++; // 找到不超过 x 的深度的最大的 2 ^ k
for (int j = K; j >= 0; j--) { // 让 x 往上跳,跳到与 y 同一高度处
if (d[f[x][j]] >= d[y]) {
x = f[x][j];
}
}
if (x == y) return x; // 如果两个点相等,那么 y 是 x 的祖先
for (int j = K; j >= 0; j--) { // 同时往上跳,跳到尽量高,但要求跳到的点不同
if (f[x][j] != f[y][j]) {
x = f[x][j];
y = f[y][j];
}
}
return f[x][0]; // 此时 x 和 y 的父亲节点就是 LCA 了
}
int main() {
int Q;
cin >> n >> Q;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1);
init();
while (Q--) {
int x, y;
cin >> x >> y;
cout << lca(x, y) << endl;
}
return 0;
}
割点
( ext{Tarjan}) 算法
割点:在一个 无向连通图 中,如果删除某个点和这个点关联的所有边,剩下图的连通分量大于 (1) ,也即剩下的图不再连通,那么我们称这个点是 割点。
int n, m, times, dfn[N], low[N];
bool iscut[N]; // 标记是否是割点
struct edge {
int u, v;
int next;
} E[M];
int p[N], eid;
void add(int u, int v) {
E[eid].u = u;
E[eid].v = v;
E[eid].next = p[u];
p[u] = eid++;
}
void dfs(int u, int fa) {
dfn[u] = low[u] = ++times;
int child = 0; // 用来处理根结点子结点数
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (!dfn[v]) { // v 没有被访问过,u, v 是树边
child++;
dfs(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) iscut[u] = true;
}
else if (v != fa) { // 反向边,注意 v == fa 的时候,是访问重复的边
low[u] = min(low[u], dfn[v]);
}
}
if (fa < 0 && child == 1) {
// fa < 0 表示根结点,之前根结点一定会被标记为割点, 取消之
iscut[u] = false;
}
}
int main() {
memset(p, -1, sizeof(p));
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs(1, -1);
for (int i = 1; i <= n; i++) {
if (iscut[i]) cout << i << " ";
}
cout << endl;
return 0;
}
点双连通分量
( ext{Tarjan}) 算法
点双连通图:一个 无向连通图 ,对于任意一个点,如果删除这个点和这个点关联的所有边,剩下的图还是连通的,那么称这个图是一个 点双连通图,也就是点双连通图中不会有割点出现。
点双连通分量:无向图 (G) 的的所有子图 (G')中,如果 (G') 是一个点双连通图,则称图 (G') 为图 (G) 的点双连通子图。如果一个点双连通子图 (G') 不是任何一个点双连通子图的真子集(包含但不相等),则图 (G') 为图 G 的 极大点双连通子图,也称为点双连通分量。
int n, m, dfn[N], low[N], cnt, times; // cnt 为点双连通分量数量
bool cut[N]; // 标记是否是割点
set<int> bcc[N]; // 记录每个点双连通分量里面的点,用 set 可以去重
struct edge {
int u, v;
int next;
} E[N];
stack<edge> S;
int p[N], eid;
void add(int u, int v) {
E[eid].u = u;
E[eid].v = v;
E[eid].next = p[u];
p[u] = eid++;
}
void dfs(int u, int fa) {
dfn[u] = low[u] = ++times;
int child = 0; // 用来处理根结点子结点数
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (!dfn[v]) { // v 没有被访问过,u, v 是树边
child++;
S.push(E[i]);
dfs(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) {
cut[v] = true;
cnt++; // 增加一个点双连通分量
edge x;
do {
x = S.top();
S.pop();
bcc[cnt].insert(x.u);
bcc[cnt].insert(x.v);
} while (x.u != u || x.v != v);
}
}
else if (v != fa) { // 反向边,注意 v == fa 的时候,是访问重复的边
low[u] = min(low[u], dfn[v]);
}
}
if (fa < 0 && child == 1) {
// fa < 0 表示根结点,之前根结点一定被标记为割点, 取消之
cut[u] = false;
}
}
int main() {
memset(p, -1, sizeof(p));
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs(1, -1);
cout << cnt << endl;
for (int i = 1; i <= cnt; i++) {
for (auto x : bcc[i]) cout << x << " ";
cout << endl;
}
return 0;
}
边双连通分量
( ext{Tarjan}) 算法
桥:在一个 无向连通图 中,如果删除某条边,剩下图的连通分量的个数大于 (1) ,也即剩下的图不再连通,那么我们称这条边是 桥。
求桥的程序与割点程序类似,略。
边双连通图:一个 无向连通图 ,对于任意一条边,如果删除这条边,剩下的图还是连通的,那么称这个图是一个 边双连通图,也就是边双连通图中不会有桥出现。
边双连通分量:无向图图 (G) 的的所有子图 (G') 中,如果 (G') 是一个边双连通图,则称图 (G') 为图 (G) 的 边双连通子图。
如果一个边双连通子图 (G') 不是任何一个边双连通子图的真子集,则 (G') 为图 (G) 的 极大边双连通子图,也称为 边双连通分量。*
注意:一个点只可能存在于一个边双连通分量。
int n, m, times, dfn[N], low[N];
struct edge {
int u, v;
int next;
} E[N];
int p[N], eid, bcc_cnt; // bcc_cnt 为边双连通分量数量
stack<int> S;
vector<int> bcc[N]; // 记录每个点双连通分量里面的点
void add(int u, int v) {
E[eid].u = u;
E[eid].v = v;
E[eid].next = p[u];
p[u] = eid++;
}
void dfs(int u, int fa) {
dfn[u] = low[u] = ++times;
S.push(u);
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (!dfn[v]) { // v 没有被访问过,u, v 是树边
dfs(v, u);
low[u] = min(low[u], low[v]);
}
else if (v != fa) { // 反向边,注意 v == fa 的时候,是访问重复的边
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) { // 此时 u 是根结点或者 fa -> u 是桥
bcc_cnt++; // 增加一个边双连通分量
int x;
do { //从栈中弹出 u 及 u 之后的顶点
x = S.top();
S.pop();
bcc[bcc_cnt].push_back(x);
} while (x != u);
}
}
int main() {
memset(p, -1, sizeof(p));
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs(1, -1);
cout << bcc_cnt << endl;
for (int i = 1; i <= bcc_cnt; i++) {
for (auto x : bcc[i]) cout << x << " ";
cout << endl;
}
return 0;
}
强连通分量
( ext{Tarjan}) 算法
强连通分量:如果 有向图 (G) 中任意两个点都 相互可达,则称图 (G) 是一个 强连通图。
代码后部有缩点。
int n, m, times, dfn[N], low[N];
struct edge {
int v;
int next;
} E[N];
int p[N], eid;
int scc_cnt; // 强连通分量数量
int sccno[N]; // 记录每个点属于的强连通分量的编号
stack<int> S;
vector<int> scc[N];
vector<int> G[N];
void dfs(int u) {
dfn[u] = low[u] = ++times;
S.push(u);
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (!dfn[v]) { // v 没有被访问过,u, v 是树边
dfs(v);
low[u] = min(low[u], low[v]);
}
else if (!sccno[v]) { // 对于已经求出 scc 的点,直接删除
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) { // u 是第一个被探测到的点
int x;
scc_cnt++;
do {
x = S.top();
S.pop();
sccno[x] = scc_cnt;
scc[scc_cnt].push_back(x);
} while (x != u);
}
}
void add(int u, int v) {
E[eid].v = v;
E[eid].next = p[u];
p[u] = eid++;
}
int main() {
memset(p, -1, sizeof(p));
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
add(u, v);
}
for (int i = 1; i <= n; i++) { // 每个点都要尝试 dfs 一次
if (!dfn[i]) {
dfs(i);
}
}
cout << scc_cnt << endl;
for (int i = 1; i <= scc_cnt; i++) {
for (auto x : scc[i]) cout << x << " ";
cout << endl;
}
// 缩点
for (int u = 1; u <= n; u++) {
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (sccno[u] != sccno[v]) G[sccno[u]].push_back(sccno[v]); // 构建新图
}
}
return 0;
}
查分约束系统
差分约束系统:是最短路的一类经典应用。 如果一个不等式组由 (n) 个变量和 (m) 个约束条件组成,且每个约束条件都是形如 (x_j - x_i le k,1 le i,j le n) 的不等式,则称其为 差分约束系统 (system of difference constraints)。
差分约束系统是求解一组变量的不等式组的算法。
两种连边方式:
-
连边后求最短路的方法,对于 (x_j - x_i le k) ,变形为 (x_j le x_i + k) 从 (i) 到 (j) 连一条权值为 (k) 的边,加入超级源点,最后求出最短路。实际上表示在 (x_i le 0) 的情况下,所有 (x) 的 最大 的解。若存在负环,就无解。
-
连边后求最长路的方法,对于 (x_j - x_i le k) ,变形为 (x_i ge x_j - k) 从 (i) 到 (j) 连一条权值为 (-k) 的边,加入超级源点,最后求出最长路。实际上表示在 (x_i ge 0) 的情况下,所有 (x) 的 最小 的解。若存在正环,就无解。
最短路
struct edge {
int v, w, next;
} E[M];
int n, m, d[N], cnt[N], p[N], eid;
void add(int u, int v, int w) {
E[eid].v = v;
E[eid].w = w;
E[eid].next = p[u];
p[u] = eid++;
}
bool inqueue[N];
bool spfa() {
memset(d, 0x3f, sizeof(d));
queue<int> q;
q.push(0);
d[0] = 0;
inqueue[0] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
inqueue[u] = false;
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v, w = E[i].w;
if (d[v] > d[u] + w) {
d[v] = d[u] + w;
if (!inqueue[v]) {
cnt[v] = cnt[u] + 1;
inqueue[v] = true;
q.push(v);
if (cnt[v] > n + 1) return false;
}
}
}
}
return true;
}
int main() {
cin >> n >> m;
memset(p, -1, sizeof(p));
for (int i = 1; i <= m; i++) {
int op, u, v, w;
cin >> op;
cin >> u >> v >> w;
if (op == 1) { // 表示 x_u - x_v <= w,则 x_u <= x_v + w
add(v, u, w);
}
else if (op == 2) { // 表示 x_u - x_v >= w,则 x_v <= x_u - w
add(u, v, -w);
}
else { // 表示 x_u - x_v = w,则 x_u <= x_v + w , x_v <= x_u - w
add(u, v, -w);
add(v, u, w);
}
}
for (int i = 1; i <= n; i++) {
add(0, i, 0);
}
if (!spfa()) {
cout << "NO" << endl;
}
else {
for (int i = 1; i <= n; i++) cout << d[i] << " ";
cout << endl;
}
return 0;
}
最长路
struct edge {
int v, w, next;
} E[M];
int n, m, d[N], cnt[N], p[N], eid;
void add(int u, int v, int w) {
E[eid].v = v;
E[eid].w = w;
E[eid].next = p[u];
p[u] = eid++;
}
bool inqueue[N];
bool spfa() {
memset(d, -1, sizeof(d));
queue<int> q;
q.push(0);
d[0] = 0;
inqueue[0] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
inqueue[u] = false;
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v, w = E[i].w;
if (d[v] < d[u] + w) {
d[v] = d[u] + w;
if (!inqueue[v]) {
cnt[v] = cnt[u] + 1;
inqueue[v] = true;
q.push(v);
if (cnt[v] > n + 1) return false;
}
}
}
}
return true;
}
int main() {
cin >> n >> m;
memset(p, -1, sizeof(p));
for (int i = 1; i <= m; i++) {
int op, u, v, w;
cin >> op;
cin >> u >> v >> w;
if (op == 1) { // 表示 x_u - x_v <= w,则 x_v >= x_u - w
add(u, v, -w);
}
else if (op == 2) { // 表示 x_u - x_v >= w,则 x_u >= x_v + w
add(v, u, w);
}
else { // 表示 x_u - x_v = w,则 x_v >= x_u - w , x_u >= x_v + w
add(u, v, -w);
add(v, u, w);
}
}
for (int i = 1; i <= n; i++) {
add(0, i, 0);
}
if (!spfa()) {
cout << "NO" << endl;
}
else {
for (int i = 1; i <= n; i++) cout << d[i] << " ";
cout << endl;
}
return 0;
}
(2- ext{SAT}) 问题
(2- ext{SAT}) :给出一些 (and,or) 等关系(如 (a_x) (and) (a_y) (= 1)) ,求出一组解。
这里给出比较暴力的一种方法
int n, m;
struct edge {
int v, next;
} E[N];
int p[N * 2], eid, c;
bool mark[N * 2];
int S[N * 2];
void add(int u, int v) {
E[eid].v = v;
E[eid].next = p[u];
p[u] = eid++;
}
bool dfs(int u) {
if (mark[u ^ 1]) return false;
if (mark[u]) return true;
mark[u] = true;
S[c++] = u;
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (!dfs(v)) return false;
}
return true;
}
bool check() {
for (int i = 1; i <= n * 2 ; i += 2) {
if (!mark[i] && !mark[i + 1]) {
c = 0;
if (!dfs(i)) {
while (c > 0) {
mark[S[--c]] = false;
}
if (!dfs(i + 1)) return false;
}
}
}
return true;
}
int main() {
memset(p, -1, sizeof(p));
cin >> n >> m;
for (int i = 0; i < m; i++) {
int k, x, y;
cin >> k >> x >> y;
if (k == 0) { // x and y = 1
add(2 * x, 2 * x + 1);
add(2 * y, 2 * y + 1);
}
else { // x or y = 1
add(2 * x, 2 * y + 1);
add(2 * y, 2 * x + 1);
}
}
if (check()) {
cout << "YES" << endl;
}
else {
cout << "NO" << endl;
}
return 0;
}
线段树
区间修改,区间求和(最值相似)
注意:(d) 数组和 (lazy) 标记数组开 (4) 倍大小
ll n, m, a[N], d[4 * N], lazy[4 * N];
void push_up(int p) {
d[p] = d[p << 1] + d[p << 1 | 1];
}
void push_down(int p, int l, int r) {
if (lazy[p]) {
lazy[p << 1] += lazy[p];
lazy[p << 1 | 1] += lazy[p];
int mid = (l + r) >> 1;
d[p << 1] += lazy[p] * (mid - l + 1);
d[p << 1 | 1] += lazy[p] * (r - mid);
lazy[p] = 0;
}
}
void build(int p, int l, int r) {
if (l == r) {
d[p] = a[l];
return ;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
push_up(p);
}
void update(int p, int l, int r, int x, int y, ll v) {
if (x <= l && r <= y) {
lazy[p] += v;
d[p] += v * (r - l + 1);
return ;
}
push_down(p, l, r);
int mid = (l + r) >> 1;
if (x <= mid) update(p << 1, l, mid, x, y, v);
if (y > mid) update(p << 1 | 1, mid + 1, r, x, y, v);
push_up(p);
}
ll query(int p, int l, int r, int x, int y) {
if (x <= l && r <= y) {
return d[p];
}
push_down(p, l, r);
int mid = (l + r) >> 1;
ll res = 0;
if (x <= mid) res += query(p << 1, l, mid, x, y);
if (y > mid) res += query(p << 1 | 1, mid + 1, r, x, y);
return res;
}
int main() {
n = rd(); m = rd();
for (int i = 1; i <= n; i++) a[i] = rd();
build(1, 1, n);
while (m--) {
ll op = rd(), x = rd(), y = rd(), k;
if (op == 1) {
k = rd();
update(1, 1, n, x, y, k);
}
else {
printf("%lld
", query(1, 1, n, x, y));
}
}
return 0;
}
树状数组
单点修改,区间求和(区间修改不会)
int C[N], n;
int lowbit(int x) {
return x & (-x);
}
int getsum(int x) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
res += C[i];
}
return res;
}
void update(int x, int v) {
for (int i = x; i <= n; i += lowbit(i)) {
C[i] += v;
}
}
int query(int l, int r) {
return getsum(r) - getsum(l - 1);
}
int main() {
int q;
cin >> n >> q;
for (int i = 1; i <= n; i++) {
int v;
cin >> v;
update(i, v);
}
while (q--) {
int op, l, r;
cin >> op >> l >> r;
if (op == 1) update(l, r);
else cout << query(l, r) << endl;
}
return 0;
}
区间修改求值
差分序列算法
int n, a[N], d[N];
int main() {
int Q;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
d[i] = a[i] - a[i - 1];
}
cin >> Q;
while (Q--) {
int l, r, v;
cin >> l >> r >> v;
d[l] += v;
d[r + 1] -= v;
}
for (int i = 1; i <= n; i++) {
a[i] = a[i - 1] + d[i];
cout << a[i] << " ";
}
return 0;
}
欧拉回路/路径
若图 (G) 中存在这样一条路径,使得它恰好通过 GG 中每条边一次,则称该路径为 欧拉路径。若该路径是一个环路,则称为 欧拉(Euler)回路。
形象一点说,欧拉路问题就是一笔画问题,你可以从某个点开始一笔画出这个图,那这个图就具有欧拉路径,具体路径就是你画的这条,如果画完正好回到起点,那这就是一条欧拉回路。
具有欧拉回路的图称称为 欧拉图。
具有欧拉路径但不具有欧拉回路的图称为 半欧拉图。
无向图判欧拉回路/路径
判断方法:
- 一个无向图 (G) 存在欧拉路径当且仅当无向图 (G) 是连通图,且该图中有两个奇度顶点(度数为奇数)或者无奇度顶点。
- 当无向图 (G) 是包含两个奇度顶点的连通图时,(G) 的欧拉路径必定以这两个奇度顶点为端点。
- 一个无向图 (G) 存在欧拉回路当且仅当无向图 (G) 连通且不存在奇度顶点。
vector<int> G[105];
int fa[105], degree[105];
int n, m, f, cnt;
void init() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int get(int x) {
if (fa[x] == x) {
return x;
}
return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
x = get(x);
y = get(y);
if (x != y) {
fa[y] = x;
f--;
}
}
int main() {
cin >> n >> m;
init();
f = n;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
merge(u, v);
degree[u]++;
degree[v]++;
}
if (f != 1) {
cout << "No" << endl;
return 0;
}
for(int i=1;i<=n;i++) {
if(degree[i]%2==1) {
cnt++;
}
}
if(cnt==0) {
cout<<"Euler loop"<<endl; //欧拉回路:欧拉路径是一个环
}
else if(cnt==2) {
cout<<"Euler path"<<endl;
}
else {
cout<<"No"<<endl;
}
return 0;
}
无向图求欧拉路径
struct node {
int v;
int id;
node (int vv, int iid) {
v = vv;
id = iid;
}
};
vector<node> G[10005];
int out[10005];
int n, m;
bool vis[100005];
void dfs(int u) {
if (out[u]) {
for (int i = 0; i < G[u].size(); i++) {
if (!vis[G[u][i].id]) {
vis[G[u][i].id] = true;
out[u]--;
out[G[u][i].v]--;
dfs(G[u][i].v);
}
}
}
cout << u << endl;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(node(v, i));
G[v].push_back(node(u, i));
out[u]++;
out[v]++;
}
dfs(1);
return 0;
}
有向图判欧拉回路/路径
判断方法:
-
一个有向图 (G) 存在欧拉路径当且仅当 (G) 是弱连通的有向图(将有向边全部看成无向边后该图是连通图),且满足以下两个条件之一:
- 所有顶点的入度和出度相等;
- 有一个顶点的出度与入度之差为 (1),一个顶点的出度与入度之差为 (-1),其余顶点的入度和出度相等。
-
当有向图 (G) 包含两个入度和出度不相同的顶点且有欧拉路径时,欧拉路径必定以这两个入度出度不相同的顶点为端点。
-
一个有向图 (G) 存在欧拉回路当且仅当图 (G) 是连通的有向图,且所有顶点的入度和出度相等。
vector<int> G[105];
int fa[105], in[105], out[105];
int n, m, f, cntout, cntin;
void init() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int get(int x) {
if (fa[x] == x) {
return x;
}
return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
x = get(x);
y = get(y);
if (x != y) {
fa[y] = x;
f--;
}
}
int main() {
cin >> n >> m;
init();
f = n;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
merge(u, v);
out[u]++;
in[v]++;
}
if (f != 1) {
cout << "No" << endl;
return 0;
}
for(int i=1;i<=n;i++) {
if(out[i]-in[i]==1) {
cntout++;
}
else if(out[i]-in[i]==-1) {
cntin++;
}
else if(out[i]!=in[i]) {
cout<<"No"<<endl;
return 0;
}
}
if(cntout==0 && cntin==0) {
cout<<"Euler loop"<<endl;
}
else if(cntout==1 && cntin==1) {
cout<<"Euler path"<<endl;
}
else {
cout<<"No"<<endl;
}
return 0;
}
有向图求欧拉路径
struct node {
int v;
int id;
node () {
}
node (int vv, int iid) {
v = vv;
id = iid;
}
};
vector<node> G[105];
int fa[105], in[105], out[105];
int n, m, f, cntout, cntin, start;
stack<int> s;
bool vis[10005];
void init() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int get(int x) {
if (fa[x] == x) {
return x;
}
return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
x = get(x);
y = get(y);
if (x != y) {
fa[y] = x;
f--;
}
}
int euler() {
if (f != 1) {
return 0;
}
for (int i = 1; i <= n; i++) {
if (out[i] - in[i] == 1) {
cntout++;
} else if (out[i] - in[i] == -1) {
cntin++;
} else if (out[i] != in[i]) {
return 0;
}
}
if (cntout == 0 && cntin == 0) {
return 1;
} else if (cntout == 1 && cntin == 1) {
for (int i = 1; i <= n; i++) {
if (out[i] - in[i] == 1) {
return i;
}
}
} else {
return 0;
}
}
void dfs(int u) {
if(out[u]) {
for(int i=0;i<G[u].size();i++) {
if(!vis[G[u][i].id]) {
vis[G[u][i].id]=true;
out[u]--;
dfs(G[u][i].v);
}
}
}
s.push(u);
}
int main() {
cin >> n >> m;
init();
f = n;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(node(v, i));
merge(u, v);
out[u]++;
in[v]++;
}
start = euler(); // 找开始的点
if (start) {
dfs(start);
while(!s.empty()) {
cout<<s.top()<<endl;
s.pop();
}
} else {
cout << "No" << endl;
}
return 0;
}
次小生成树
int n, m;
struct node {
int u, v, next;
ll w;
} a[3 * N], E[3 * N];
bool cmp(node x, node y) {
return x.w < y.w;
}
int p[N], eid, fa[N], f[N][22], d[N];
ll mx[N][22], cmx[N][22], W;
vector<node> G[3 * N];
bool used[3 * N];
void add(int u, int v, ll w) {
E[eid].u = u;
E[eid].v = v;
E[eid].next = p[u];
E[eid].w = w;
p[u] = eid++;
}
void init() {
memset(mx, -1, sizeof(mx));
memset(cmx, -1, sizeof(cmx));
memset(p, -1, sizeof(p));
eid = 1;
for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) {
if (fa[x] == x) return fa[x];
return fa[x] = find(fa[x]);
}
void kruskal() {
sort(a, a + m, cmp);
for (int i = 0; i < m; i++) {
int fu = find(a[i].u), fv = find(a[i].v);
if (fu != fv) {
fa[fv] = fu;
W += a[i].w;
add(a[i].u, a[i].v, a[i].w);
add(a[i].v, a[i].u, a[i].w);
used[i] = true;
}
}
}
void dfs(int u) {
d[u] = d[f[u][0]] + 1;
for (int i = p[u]; i != -1; i = E[i].next) {
int v = E[i].v;
if (v != f[u][0]) {
mx[v][0] = E[i].w;
cmx[v][0] = -INF;
f[v][0] = u;
dfs(v);
}
}
}
void init_LCA() {
for (int i = 1; i <= n; i++) fa[i] = 0;
dfs(1);
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i <= n; i++) {
f[i][j] = f[f[i][j - 1]][j - 1];
mx[i][j] = max(mx[i][j - 1], mx[f[i][j - 1]][j - 1]);
cmx[i][j] = max(cmx[i][j - 1], cmx[f[i][j - 1]][j - 1]);
if (mx[i][j - 1] > mx[f[i][j - 1]][j - 1]) cmx[i][j] = max(cmx[i][j], mx[f[i][j - 1]][j - 1]);
if (mx[i][j - 1] < mx[f[i][j - 1]][j - 1]) cmx[i][j] = max(cmx[i][j], mx[i][j - 1]);
}
}
}
ll LCA(int x, int y, ll w) {
if (d[x] < d[y]) swap(x, y);
ll maxv = -INF;
for (int i = 20; i >= 0; i--)
if (d[f[x][i]] >= d[y]) {
if (mx[x][i] != w) maxv = max(maxv, mx[x][i]);
else maxv = max(maxv, cmx[x][i]);
x = f[x][i];
}
if (x == y) return maxv;
for (int i = 20; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
if (mx[x][i] != w) maxv = max(maxv, mx[x][i]);
else maxv = max(maxv, cmx[x][i]);
if (mx[y][i] != w) maxv = max(maxv, mx[y][i]);
else maxv = max(maxv, cmx[y][i]);
x = f[x][i];
y = f[y][i];
}
}
if (mx[x][0] != w) maxv = max(maxv, mx[x][0]);
else maxv = max(maxv, cmx[x][0]);
if (mx[y][0] != w) maxv = max(maxv, mx[y][0]);
else maxv = max(maxv, cmx[y][0]);
return maxv;
}
ll getlen(int u, int v, ll w) {
ll maxv = LCA(u, v, w);
return W + w - maxv;
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) cin >> a[i].u >> a[i].v >> a[i].w;
init();
kruskal();
init_LCA();
ll ans = INF;
for (int i = 0; i < m; i++)
if (!used[i])
ans = min(ans, getlen(a[i].u, a[i].v, a[i].w));
cout << ans << endl;
return 0;
}