服了,看来只写题解不系统总结的博客并不能十分有效地提高对知识点的理解程度。还是得回归到以前既总结知识本身,又写题解的形态。否则就会落到今天这个下场——知识点大量遗忘,会的题也不会做了……这里大概说说网络流的一些脱产应用(我最容易忘的一些),后续还会添加如何应用这些东西的思想罢。
最大权闭合子图
定义:
一个有向图 \(G = (V,E)\) 的闭合图是该有向图的一个点集,且该点集的所有出边都指向该点集,即闭合图内的任意后继也一定在闭合图中。
如何求解:
思路:建图,利用最小割模型来解。
设定一个源点 \(s\),将 \(s\) 向所有点权为 \(v(v > 0)\) 的点连一条最大流量为 \(v\) 的边。设定一个汇点 \(t\),从所有点权为 \(v(v < 0)\) 的点向汇点 \(t\) 连一条最大流量为 \(-v\) 的边。再将原图中所有边在新建的图中相连,容量为正无穷。跑一遍最小割,用总点权减去跑出来的点权即可。
最大密度子图
定义:一个无向图 \(G = (V,E)\) 的密度 \(D\) 为该图的边数 \(E\) 与该图的点数 \(V\) 的比值 \(D = \frac{|E|}{|V|}\)。给出一个无向图 \(G = (V,E)\),其具有最大密度的子图 \(G'=(V',E')\) 称为最大密度子图,即最大化 \(D'=\frac{E'}{V'}\)
解决方法:
对 \(D\) 换一种写法:
\[D = f(x) = \frac{\sum_{e \in E}x_e}{\sum_{v \in V}x_v} \]\(x_e\) 若等于 \(1\),则表示选择了 \(e\) 这条边,\(x_v\) 亦如此。
你需要对每一个 \(v\) 做出选还是不选的决定,对每一个 \(e\),如果选择它,就必须也选择它的两个端点(又是一种捆绑的感觉)。
我们不妨考虑下分数规划。
构造一个新函数:
\[h(g) = \max(\sum_{e \in E}x_e - \sum_{v \in V}g * x_v) \]因为此时所有点的点权为 \(1\),所以枚举一个 \(g\) 就相当于把选的这些点的点权全部赋值为 \(g\),根据这些点推出所必须选的边的数量,然后就可以根据 \(h(g)\) 的正负判断结果了。
\[\left\{ \begin{matrix} h(g) = 0 \to g = D_{max}\\ h(g) < 0 \to g > D_{max}\\ h(g) > 0 \to g < D_{max}\\ \end{matrix} \right. \]}
有一个小结论:同一个图内两种密度的差最小为 \(\frac{1}{n^2}\),算很显然的了吧。
贴个板子~
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
const int MAXN = 1e5;
int n,m,tot,cnt,dis[MAXN + 5],head[MAXN + 5],cur[MAXN + 5],s,t,x[MAXN + 5],y[MAXN + 5];
struct EDGE{
int u,v,next;
double w;
}edge[MAXN + 5];
void ADD(int u,int v,double w){
++tot;
edge[tot].next = head[u];
edge[tot].u = u;
edge[tot].v = v;
edge[tot].w = w;
head[u] = tot;
}
void add(int u,int v,double w){
ADD(u,v,w);
ADD(v,u,0);
}
void build(double r){
tot = 1;
cnt = n;
memset(head,0,sizeof head);
memset(edge,0,sizeof edge);
for(int i = 1; i <= m; i++){
++cnt;
add(s,cnt,1);
add(cnt,x[i],INF);
add(cnt,y[i],INF);
}
t = ++cnt;
for(int i = 1; i <= n; i++)add(i,t,r);
}
bool bfs(){
queue<int> q;
q.push(s);
memset(dis,0,sizeof dis);
dis[s] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for(int i = head[u]; i; i = edge[i].next){
int v = edge[i].v;
if(!dis[v] && edge[i].w > eps){
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if(dis[t])return 1;
return 0;
}
double dfs(int u,double dist){
if(u == t)return dist;
for(int &i = cur[u]; i; i = edge[i].next){
int v = edge[i].v;
if(dis[v] == dis[u] + 1 && edge[i].w > eps){
double di = dfs(v,min(dist,edge[i].w));
if(di > eps){
edge[i].w -= di;
edge[i ^ 1].w += di;
return di;
}
}
}
return 0;
}
double dinic(){
double aans = 0;
while(bfs()){
for(int i = 0; i <= cnt; i++)cur[i] = head[i];
while(double di = dfs(s,INF))aans += di;
}
return aans;
}
bool check(double r){
build(r);
double b = dinic();
return m - b > eps;
}
int vis[MAXN + 5],ans;
void DFS(int u)
{
vis[u] = 1, ans += (u <= n);
for(int i = head[u]; i; i = edge[i].next)
if(edge[i].w >= eps && !vis[edge[i].v]) DFS(edge[i].v);
}
int main(){
scanf("%d%d",&n,&m);
if(m == 0){cout << 1;return 0;}
for(int i = 1; i <= m; i++){
scanf("%d%d",&x[i],&y[i]);
}
double l = 0,r = m,mn = 1.0 / n / n;
while(l + mn < r){
double mid = (l + r) / 2.0;
if(check(mid))l = mid;
else r = mid;
}
check(l);
DFS(s);
cout << ans - 1;
}
二分图的最大匹配
定义:
给定一个二分图,从这个图中的所有边挑一些边出来形成一个新边集,使得集合中任意两条边都没有公共点。这样的一个边集叫二分图的一个匹配。而这种元素最多的集合称为二分图的最大匹配。
解决方法:
将二分图分为左部和右部。从源点向左部每一个点各连一条最大容量为 \(1\) 的边,从右部的所有点向汇点各连一条最大容量为 \(1\) 的边。将原图中所有无向边的方向全改为从左部到右部,最大容量为正无穷。把这些边加入到新建的图中,跑一遍最大流即可。
二分图的最小点权覆盖集
定义:
点覆盖集是无向图 \(G\) 的一个点集,使得该图中所有边都至少有一个端点在这个集合内。而最小点覆盖集就是点数最少的覆盖集。
解决方法:
有个定理,二分图的最小点权覆盖集等于二分图的最大匹配……
二分图的最大点权独立集
定义:
点独立集就是在一个无向图中选一些点,使这些点两两间没有边相连即可。
解决方法:
所有点的数量减去最小点权覆盖集即可。
标签:知识点,int,double,点权,tot,edge,生草,梳理,dis From: https://www.cnblogs.com/CZ-9/p/17117009.html