严格次小生成树
次小生成树的原理:次小生成树与最小生成树仅有一条边不同
遍历所有没有加入最小生成树的边
找到边连接的两点的lca。
使用st表找到两点到lca的路径上的最小值和次小值(严格)
typedef long long ll;
const int N=100100,M=300100;
const int inf=0x3f3f3f3f;
struct edge{
int val,from,to;
}e[M];
bool vis[M*2];
int h[N],to[M*2],val[M*2],ne[M*2];
int cnt;
void add( int a,int b,int c){
val[cnt]=c,to[cnt]=b,ne[cnt]=h[a],h[a]=cnt++;
}
int cmp( edge a,edge b){
return a.val<b.val;
}
int p[N];
int find( int x){
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
int fa[N][17],depth[N];
int d1[N][17],d2[N][17];
queue<int>q;
void bfs(){
q.push(1);
memset(depth,-1,sizeof(depth));
depth[1]=1;
while(!q.empty()){
int now=q.front();
q.pop();
for( int i=h[now];i!=-1;i=ne[i]){
int j=to[i];
if(depth[j]!=-1) continue;
// cout<<j<<endl;
q.push(j);
depth[j]=depth[now]+1;
fa[j][0]=now;
d1[j][0]=val[i],d2[j][0]=-inf;
for( int x=1;x<=16;x++){
int mid=fa[j][x-1];
fa[j][x]=fa[ mid ][ x-1 ];
int dis[4]={d1[j][x-1],d2[j][x-1],d1[mid][x-1],d2[mid][x-1]};
for( int y=0;y<4;y++){
int d=dis[y];
if(d>d1[j][x]) d2[j][x]=d1[j][x],d1[j][x]=d;
else if(d<d1[j][x]&&d>d2[j][x]) d2[j][x]=d;
}
}
}
}
}
int lca(int x,int y,int val){//x在y的下面
if(depth[x]<depth[y]) swap(x,y);
int res=-inf;
for( int i=16;i>=0;i--){
if(depth[fa[x][i]]>=depth[y]) {
if(d1[x][i]!=val) res=max(res,d1[x][i]);
if(d2[x][i]!=val) res=max(res,d2[x][i]);
x=fa[x][i];
}
}
if(x==y) return res;
for( int i=16;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
if(d1[x][i]!=val) res=max(res,d1[x][i]);
if(d2[x][i]!=val) res=max(res,d2[x][i]);
if(d1[y][i]!=val) res=max(res,d1[y][i]);
if(d2[y][i]!=val) res=max(res,d2[y][i]);
x=fa[x][i],y=fa[y][i];
}
}
if(d1[x][0]!=val) res=max(res,d1[x][0]);
if(d2[x][0]!=val) res=max(res,d2[x][0]);
if(d1[y][0]!=val) res=max(res,d1[y][0]);
if(d2[y][0]!=val) res=max(res,d2[y][0]);
// cout<<x<<endl;
return res;
}
int main(){
int n,m;
ll ans=0;
cin>>n>>m;
memset(h,-1,sizeof(h));
for( int i=1;i<=n;i++) p[i]=i;
for( int i=1;i<=m;i++){
scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].val);
}
sort(e+1,e+1+m,cmp);
for( int i=1;i<=m;i++){
if(find(e[i].from)!=find(e[i].to)) {
p[find(e[i].from)]=find(e[i].to);
vis[i]=1;
add(e[i].from,e[i].to,e[i].val);
add(e[i].to,e[i].from,e[i].val);
// cout<<e[i].from<<e[i].to<<endl;
ans+=e[i].val;
}
}
bfs();
ll res=1e18;
for( int i=1;i<=m;i++){
if(vis[i]==0){
int lc=lca(e[i].from,e[i].to,e[i].val);
res=min(ans-lc+e[i].val,res);
}
}
cout<<res<<endl;
}
最大流
最大流的原理是:
找到一个可行流,再迭代进行以下步骤:
更新残流网络,再从残流网络中找到一个可行流。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 1010
int n, m;
int maxflow = 0;
int ne[N], val[N], to[N], h[N];
int cnt;
void add(int a, int b, int c)
{
val[cnt] = c, to[cnt] = b, ne[cnt] = h[a], h[a] = cnt++;
val[cnt] = 0, to[cnt] = a, ne[cnt] = h[b], h[b] = cnt++;
***cnt为偶数时是正向边,cnt为奇数时是反向边。
}
int pre[210]; 记录每个点的前驱节点
int flow[210]; //流入每个点的最大流量
int inedge[210]; //记录通向每个点的边,与前驱节点配合还原路径。
int bfs(int start, int end)
{ //每次dfs只选择一条无分叉路
int vis[210];
memset(vis, 0, sizeof(vis));
memset(flow, 0, sizeof(flow));
queue<int> q;
q.push(start);
vis[start] = 1;
flow[start] = 0x7fffffff;
while (!q.empty())
{
int now = q.front();
q.pop();
if (now == end)
return 1;
for (int i = h[now]; i != -1; i = ne[i])
{
if (val[i] == 0)
continue;
int j = to[i];
if (vis[j] == 0)
{
vis[j] = 1;
pre[j] = now;
inedge[j] = i;
flow[j] = min(flow[now], val[i]);
q.push(j);
}
}
}
return 0;
}
void update(int start, int end)
{
int x = end;
int f = flow[end];
while (x != start)
{
int i = inedge[x];
val[i] -= f;
val[i ^ 1] += f;
x = pre[x];
}
maxflow += f;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
for (int i = 1; i <= n; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
while (bfs(1, m))
{
update(1, m);
}
cout << maxflow << endl;
return 0;
}
费用流
费用流是指存在多个最大流时,找到费用最少的最大流。
费用流目前主流解法就是最大流spfa,
待补充