网络流+二分图总结
2015-07-20 22:08
531 查看
一. Dinic跑最大流
1.模板(上限V^2*E)
//先init将放边的数组清空,再将[a, b]的g边清空,加边的时候正向边为0, 反向边为1;
//MAXN为顶点个数,每个边的flow为此边跑了多少流.正向边为正数,而反向边为负数,但只看差值即可.
//复杂度V^2E;
const int MAXN = 5000 + 10;
const int INF = 1000000000;
struct Edge
{
int from, to, cap, flow;
};
struct Dinic
{
int s, t;
vector edges; // 边数的两倍
vector G[MAXN]; // 邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
bool vis[MAXN]; // BFS使用
int d[MAXN]; // 从起点到i的距离
int cur[MAXN]; // 当前弧指针
};
Dinic g;
2.最小割=最大流. 然后最小割:最大权闭合图, 以及:对于有些顶点不能共存,比如矩阵上二分染色,相邻的不能共处,加一些不能存在的边跑最大独立集以及带权最大独立集.
3. 对于无向图可以构造两条边,dinic不担心本身有环.
4. 如果点有最大流限制,拆点加限制.并且定义好出入.如果点有最小流限制e,最大限制c,把点u拆成ua(负责接收 入) ub(负责放出 出). 在原本的s和t的基础上再搞S和T,然后,S连ub,流e, ua连T,流e. 超级S和T接收总流量就行.(因为有新的附加,在S-s,t-T跑之前,先跑一遍S和T,看有没有达到e和,防止附加值下限不对).
5. 增加流和推流,增加流找到相应的增加就行.但是推流:u->v,如果cap>flow,那么直接cap–,如果cap==flow,那么从v遍历到t,找flow>0的路,退一个流.从s找到u,一样.然后最大流–;其实也可以不用退:如果u->v找到一个增广路,就不用退,跑一个1流就行.
6. 对时间的拆点和二分思路:图中有很多个门,每个门一秒只能出去一个人.然后问图中的人均出去的最短时间.到门的分配问题,涉及到最短时间.二分枚举最短时间,然后判定.一个门一秒只能流一个人,门的t拆点.
最大权闭合图.可能需要自己先构造虚拟点,比如太空做实验的题目.正值的点放在s连,并且求一个sum,负值的点放在t,改为正值加边.然后中间有关系的不能断的搞INF.跑一遍最大流.
最小割可以用来求将图割成两块的边权.随便搞一个点最为s,枚举另一个点,求一下割.所有的最小值就是最小值.但是这样复杂度太高.可以用Stoer-Wagner算法.
最大稠密子图.在一个无向图中,找出一部分图:使得边的个数/点的个数 最大. 首先变成分数规划,g = max(e/v). 然后h(g) = max(e – gv), 是一个单调减.且答案是让h(g)=0,的g. 因此二分枚举g,然后搞出max(e-gv), 转化一下:ans = -min(g – (dv/2 – C(v,v’))). 然后ans = -min(g-dv/2 + C(v,v’)), 括号内的构图: S连每个点权值为U(很大的数,这里为它的度);相连的点关系不变,权值为1;每个点连T,权值为U+2*g-dv. 然后ans = U*n – dinic;比如下面的代码:
const int INF = 0x3f3f3f3f;
const int maxn = 110;
const int maxm = 1010;
struct node
{
int u,v;
double w;
int next,rev;
}p[maxm],edge[maxm*6];
int s,t,n,m,nn;
int cnt,head[maxn];
int d[maxn];
int dist[maxn],vis[maxn];
int ans;
void init()
{
cnt = 0;
memset(head,-1,sizeof(head));
}
void add(int u, int v, double w)
{
edge[cnt] = (struct node){u,v,w,head[u],cnt+1};
head[u] = cnt++;
}
void build(double mid)
{
init();
for(int i = 1; i <= m; i++)
{
add(p[i].u,p[i].v,1.0);
add(p[i].v,p[i].u,1.0);
}
for(int i = 1; i <= nn; i++)
{
add(s,i,m);
add(i,t,m*1.0+2*mid-d[i]*1.0);
}
}
bool bfs()
{
queue que;
memset(dist, 0, sizeof(dist));
memset(vis, 0, sizeof(vis));
while(!que.empty()) que.pop();
vis[s] = 1;
que.push(s);
while(!que.empty())
{
int u = que.front();
que.pop();
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].v;
if(edge[i].w && !vis[v])
{
que.push(v);
vis[v] = 1;
dist[v] = dist[u]+1;
}
}
}
if(dist[t] == 0)
return false;
return true;
}
double dfs(int u, double delta)
{
if(u == t) return delta;
double ret = 0,tmp;
}
double Dinic()
{
double ans = 0,res;
while(bfs())
{
while(res = dfs(s,INF))
ans += res;
}
return ans;
}
void dfs_cut(int u)
{
vis[u] = 1;
if(u <= nn) ans++;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].v;
if(edge[i].w > 0 && !vis[v])
{
dfs_cut(v);
}
}
}
int main()
{
while(~scanf(“%d %d”,&n,&m))
{
if(m == 0)
{
printf(“1\n1\n”);
continue;
}
memset(d,0,sizeof(d));
for(int i = 1; i <= m; i++)
{
scanf(“%d %d”,&p[i].u,&p[i].v);
d[p[i].u]++;
d[p[i].v]++;
}
}
有边权:求的是子图的边权和除以点数最大。此时U为所有边权的和,中间相连的点权值为we,连T权值为U+2*g-dw(dw为所有连v的边权和)
有点权和边权:求的是子图的边权和和点权和除以点数最大。此时U为所有边权和加二倍点权的和,中间相连的点权值为we,连T的权值为U+2*(g-pv)-dw
二. 二分图匹配
1. 模板(不一定有dinic快的模板……上限VE)
///max_v, V, get_G, 0 ~ V - 1;
//v*E;
const int MAXN = 2e4 + 100;
const int INF = INT_MAX/2 - 1;
vector G[MAXN];
int V;
int m, n;
int match[MAXN], used[MAXN];
void add_edge(int from, int to)
{
G[from].push_back(to);
G[to].push_back(from);
}
int dfs(int v)
{
used[v] = 1;
for(int i = 0; i < G[v].size(); i++)
{
int u = G[v][i], w = match[u];
if(w < 0 || (!used[w] && dfs(w)))
{
match[u] = v;
match[v] = u;
return 1;
}
}
return 0;
}
int b_match()
{
int res = 0;
fill(match, match + V, -1);
for(int i = 0; i < V; i++)//其实到m就行了.
{
if(match[i] < 0)
{
CLR(used);
if(dfs(i))
{
res++;
}
}
}
return res;
}
/*
//可以找出是那些点覆盖了边.
//方法:x集合中未匹配的大胆表used, 然后与之相连的y集合中的要标成used,之后只有与y相匹配的x中的点才敢标成used,再深搜次x,最终x集合中未标记的和y集合中标记//的选出来.
*/
void find_dfs(int v)
{
used[v] = 1;
REP(i, 0, G[v].size())
{
if(!used[G[v][i]])
{
used[G[v][i]] = 1;
int new_v = match[G[v][i]];
if(new_v != -1)
{
find_dfs(new_v);
}
}
}
}
void find_who()
{
CLR(used);
REP(i, 0, n)
{
if(match[i] == -1 && !used[i])
{
find_dfs(i);
}
}
REP(i, 0, n)
{
if(!used[i])//不是多余的
{
printf(” r%d”, i + 1);
}
}
REP(i, n, n + m)
{
if(used[i])//右边集合要有的.
{
printf(” c%d”, i - n + 1);
}
}
printf(“\n”);
}
2. KM算法跑边带权匹配(会将x节点找到匹配,y节点可以多 n^3)
//nx, ny, w[i][j] 有就写,没有就弄一个特别大的,使得结果可以判定.
//可能要改成double的:w, lx, ly, slack, t, t与0的比较, d;
const int maxn = 200;
const int INF = 1e6;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int linky[maxn];
int visx[maxn], visy[maxn];
int slack[maxn];
int nx, ny;
int find(int x)
{
visx[x] = 1;
for(int y = 0;y < ny;y++)
{
if(visy[y])
continue;
int t = lx[x] + ly[y] - w[x][y];
if(t == 0)
{
visy[y] = 1;
if(linky[y] == -1 || find(linky[y]))
{
linky[y] = x;
return 1;
}
}
else if(slack[y] > t)
{
slack[y] = t;
}
}
return 0;
}
int KM()
{
memset(linky, -1, sizeof(linky));
CLR(ly);
for(int i = 0;i < nx;i++)
{
lx[i] = -INF;
for(int j = 0;j < ny;j++)
{
if(w[i][j] > lx[i])
{
lx[i] = w[i][j];
}
}
}
for(int x = 0;x < nx;x++)
{
for(int i = 0;i < ny;i++)
slack[i] = INF;
while(1)
{
CLR(visx);
CLR(visy);
if(find(x))
{
break;
}
int d = INF;
for(int i = 0;i < ny;i++)
{
if(!visy[i] && d > slack[i])
d = slack[i];
}
for(int i = 0;i < nx;i++)
{
if(visx[i])
lx[i] -= d;
}
for(int i = 0;i < ny;i++)
{
if(visy[i])
{
ly[i] += d;
}
else
{
slack[i] -= d;
}
}
}
}
int res = 0;
for(int i = 0;i < ny;i++)
{
if(linky[i] > -1)
{
res += w[linky[i]][i];
}
}
最小边覆盖点 = V-匹配. 边带权值的话:如果是二分图:KM算法. 或者是用网络流,左边要建2*n个点,右边2*m个点,因为每一个点并不是流为1,但是我们要统计流为1,这样总和才对.
最小顶点覆盖边. 二分图就等于最大匹配数. 带权的点:用最小割来解决.u->v,那么割u或者割v.和最大权独立集构造方法一样(本身就是互反的).
最大独立集.应用很广泛,不能有边的最大点的个数.总的点数-最大匹配数. 如果带权:经典的构造出来二分图,然后总和减去割.
最大独立集的思维转换.有男生和女生,有的认识.选择一个最大的集合,使得这里面的男生和女生都认识.反过来看,就是不能出现两个不认识的人.那么把不认识的人建边,找最大独立集. 逆向思维.
矩阵网格,格子里有东西,然后行和列什么的,以行和列分成二分图,格子的东西为边.
矩阵网格还经常二分图染色.比如相邻的不能存在,那么就是二分图独立集.相邻的1*2的一块涨分,那么染色后分供应的和被动接受的.
对时间的拆点和二分思路:图中有很多个门,每个门一秒只能出去一个人.然后问图中的人均出去的最短时间.到门的分配问题,涉及到最短时间.二分枚举最短时间,然后判定.一个门一秒只能流一个人,门的t拆点.
牛必须吃认定的饮料和蔬菜.每个饮料和蔬菜都有限制.问同时满足的牛个数:两个一起供应到的问题,不能用匹配.两个刚好可以饮料+牛拆a+牛拆b+蔬菜来建图.
一个原件i在A加工和在B加工代价不一样.一共n个,ij可能有连接关系,不在一块有额外的费用. 用最小割来描述很合适:sB iA t,然后如果i和j有联系,在i和j之间连一条边w. 注意,最大流都是有向流,对于i和j的边应该建两条.
n玩具在工厂里制作,有玩具需要等,求玩具等待的总时间.只有一个工厂时,是贪心,有很多工厂时,考虑每一个玩具对答案的贡献,发现对每一个玩具弄一个这样的匹配:对每一个工厂建立n个点,表示倒数第几个在工厂里将,一个点用一次,边为x*cost,然后求最小匹配.
一堆x,一堆y,给出可能配对的信息,问为了保证配对最多.一定能够确定的对数是多少.其实就是看边是不是关建边.先跑一遍二分图匹配,然后扫x集合,找到对应的y,把x和y的match都清除,边的关系也暂时清除,直接dfs(x)找增广路,能找到,说明不是关键边,之后把边补上去就行,不用还原match,如果找不到,那么要还原match,并且补上边,而且是关建边.
注意从一般图中看出来是二分图.二分图:如果分成x和y集合,在x或者y集合内部不可能存在边的就是二分图.就是说x和y有关系,y和z有关系,那么z一定和x是一个集合,并且z和x一定不能发生关系.
矩阵上的消除某些格子,把某一个格子当成边,两端的顶点往行和列上靠然后往最小点覆盖边上靠.比如:矩阵上有草地和泥,现在用1*x的木板来盖泥,不能盖草地,那么最少需要多少块木板.想象泥是一条边,那么用木板来盖它,行的连续的木板放在x集合,列的连续的木板放在Y集合.这样每一个泥坑都连一条边,之后求最小点覆盖边.
一个人可以往x走,也可以往y走,但只能选一个地方的感觉.一共有l个地方,每两个地方都有时间消耗,要求ti时刻有人在i点,至少需要几个人.一个人的话,从i走到j和k都可能,到底走到哪?其实在整个图上看是走到哪可以连接最多的边.每连一条边就能够减小1个人.所以拆点成二分图然后匹配.
最小k路径覆盖.每次只能走简单路,最少多少条能够覆盖.每个点拆成两个点,一个出,一个接收(入).然后每匹配一个相当于减少一次.魔术球问题:反着问,k条路可以走多少个点.二分枚举点,看<=k否. 于是二分枚举可以做,暴力枚举然后跑残余的二分图也可以做.其实这个二分图也可以建最大流.s向左边的每一个点(出)都搞一个1的流,右边(入)都向t搞一个1的流,然后左边可以留到右边的搞一个INF的流(因为路径可以重复).然后跑最大流.
搞多匹配问题:供应多个,接收接收多个.然后最大流是不是满足.加上费用就是花费最少了.
多线程过程中一个数的使用唯一性限定.最长递增子序列,一个数只能用一次.有几个同时最长的序列.一个数用一次,可以拆点限定.然后需要描述:走通一条流就是一个递增子序列.那么先跑n^2的f[i],只有i到j连边只有i结尾的通过上上了j然后f[j]=f[i]+1.并且s只和f[i]=1的连,f[i]=max_len的和t连.保证一条路一定是对的. 注意:如果允许某些点同时用,不能简单的放拆点的是INF.因为还涉及到和s和t的关系,到底放不成INF?
最小费用流
模板(spfa跑, F*(kn))
//get_G; V; const int MAXN = 1000 + 10;
const int INF = INT_MAX/2 - 1;
struct edge
{
int to, cap, cost, rev;
edge(int a = 0, int b = 0, int c = 0, int d = 0)
{
to = a;
cap = b;
cost = c;
rev = d;
}
};
vector G[MAXN];
int V;
void add_edge(int from, int to, int cap, int cost)
{
G[from].push_back(edge(to, cap, cost, G[to].size()));
G[to].push_back(edge(from, 0, -cost, G[from].size() - 1));
}
int dist[MAXN], inq[MAXN];
int prev_v[MAXN], prev_i[MAXN];
int spfa(int s, int t)
{
queue myq;
for(int i = 0;i < V;i++)
{
dist[i] = INF;
}
CLR(inq);
dist[s] = 0;
inq[s] = 1;
myq.push(s);
while(!myq.empty())
{
int v = myq.front();inq[v] = 0; myq.pop();
for(unsigned int i = 0;i < G[v].size();i++)
{
edge &e = G[v][i];
if(e.cap > 0 && dist[v] + e.cost < dist[e.to])
{
dist[e.to] = dist[v] + e.cost;
prev_v[e.to] = v;
prev_i[e.to] = i;
if(inq[e.to] == 0)
{
inq[e.to] = 1;
myq.push(e.to);
}
}
}
}
if(dist[t] == INF)
{
return 0;
}
else
{
return 1;
}
}
int min_cost_flow(int s, int t, int f)
{
int res = 0;
while(f > 0)
{
if(spfa(s, t) == 0)
{
return -1;
}
else
{
int d = f;
for(int v = t;v != s;v = prev_v[v])
{
d = min(d, G[prev_v[v]][prev_i[v]].cap);
}
f -= d;
res += d*dist[t];
for(int v = t;v != s;v = prev_v[v])
{
edge &e = G[prev_v[v]][prev_i[v]];
e.cap -= d;
G[e.to][e.rev].cap += d;
}
}
}
return res;
}
这个不能跑环.因为一旦有环一定会出现负环.所以要先分出来二分图再跑费用流.
对于给出的一个已经跑出来最小费用的图,如何判断是在f流下的真正最小费用?就是在残余图上跑cap>0(有效边),然后看有没有负环(spfa跑不出来).于是有一种查负环方法:用q_time记数,一旦超了,那么u返回.然后用pre_v一直找到一个真正的环.查出来之后,绕着环跑一个流就好.
一般只能跑有向图.如果有向图中边有最少流量要求.那么可以这样做:u->v,最少e,最大f,u到v建 cost-M(足够大) 一共e条流,然后f-e条cost的正常流. 最后的花费+e*M就好.
如果有向图中的花费有负边,那么本来是不能搞的,但是通过下面转换:如果最大流F一定,那么新建S和T,超级,往s和t流F,然后对于u->v,流量f,cost<0的情况,S先连到v(f,0),u先连到T(f,0),之后从v向u连-cost,(-cost是正值).最终结果在上cost(负值)*f.
多线程问题.从s走到t,再走回来,不能重复.一下跑流量为2的流. 还有最大K区间覆盖问题.分别取尝试覆盖k次,并且用到的区间边不能重复,因此跑一个k的流.特别的,对于k区间覆盖,两种方法:如果离散的点,区间作为边来跑. 或者:每个区间当做点,拆两个点,然后排序后给能够在后面取的均连一条边.
最大k覆盖的一个应用.题目:一共有10个桶,有n~1e4个球,编号ai,有一个k~1e4的序列,每次放相应编号的球,如果已经有了,不用放,没有的话挑一个免费挤跑,然后放进去,放的时候需要花wi的钱.问放的费用最小.一共有10个桶,意思就是同时不能有大于10的流出现,假设每个球都花钱,看哪些能省,相同的连一个区间,可以省,这些区间中跑最大10覆盖.就是可以省的最大权值.注意,i和i+1用的球是一个的时候,并不会占用一个桶也可以省,因此在最初的时候要消去这种情况.哈哈哈自己想到的.
如果不是流量*边费用,而是边费用的和,可能是最小树形图
纸巾.就是i天的可能给j天用.i天有要求流到t ai,但是i天同时又给其他天提供了ai的流,怎么办?拆点,右端的搞接收,然后传给t,左端的给出,并且已知左端一定会给出总的是ai的流(而且是免费获得).注意i->i+1建有向边减少总的边数.
费用流按照时间来拆点分层.注意t->t+1的无条件联通.还可能按照剩余的油值来分层,可能直接跑最短路就成了.
费用流解决多线程问题,通过拆点限制流可以限制点的使用次数.
如果将原DAG权值取反,然后从最后一关连一条正权边到第一关,权值是最短路(负权值最短路=传统意义上的最长路)的长度的话,那么那些正圈中的负权边就是应该增加权值的边,具体应该加多少,就是正圈的权值。
再看怎么用网络流计算这些正圈权值,新建源点汇点,对于所有顶点,如果入度>出度,从源点连一条边到它,否则,从它连一条边到汇点,容量都是是度数差。
还没弄懂: http://www.hankcs.com/program/algorithm/aoj-2230-how-to-create-a-good-game.html
1.模板(上限V^2*E)
//先init将放边的数组清空,再将[a, b]的g边清空,加边的时候正向边为0, 反向边为1;
//MAXN为顶点个数,每个边的flow为此边跑了多少流.正向边为正数,而反向边为负数,但只看差值即可.
//复杂度V^2E;
const int MAXN = 5000 + 10;
const int INF = 1000000000;
struct Edge
{
int from, to, cap, flow;
};
struct Dinic
{
int s, t;
vector edges; // 边数的两倍
vector G[MAXN]; // 邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
bool vis[MAXN]; // BFS使用
int d[MAXN]; // 从起点到i的距离
int cur[MAXN]; // 当前弧指针
void init() { edges.clear(); } void clearNodes(int a, int b) { for(int i = a; i <= b; i++) G[i].clear(); } void AddEdge(int from, int to, int cap) { edges.push_back((Edge) { from, to, cap, 0 }); edges.push_back((Edge) { to, from, 0, 0 }); int m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BFS() { memset(vis, 0, sizeof(vis)); queue<int> Q; Q.push(s); vis[s] = 1; d[s] = 0; while(!Q.empty()) { int x = Q.front(); Q.pop(); for(int i = 0; i < G[x].size(); i++) { Edge& e = edges[G[x][i]]; if(!vis[e.to] && e.cap > e.flow) { vis[e.to] = 1; d[e.to] = d[x] + 1; Q.push(e.to); } } } return vis[t]; } int DFS(int x, int a) { if(x == t || a == 0) return a; int flow = 0, f; for(int& i = cur[x]; i < G[x].size(); i++) { Edge& e = edges[G[x][i]]; if(d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap-e.flow))) > 0) { e.flow += f; edges[G[x][i]^1].flow -= f; flow += f; a -= f; if(a == 0) break; } } return flow; } // 求s-t最大流。如果最大流超过limit,则只找一个流量为limit的流 int Maxflow(int s, int t, int limit) { this->s = s; this->t = t; int flow = 0; while(BFS()) { memset(cur, 0, sizeof(cur)); flow += DFS(s, limit - flow); if(flow == limit) break; // 达到流量限制,直接退出 } return flow; }
};
Dinic g;
2.最小割=最大流. 然后最小割:最大权闭合图, 以及:对于有些顶点不能共存,比如矩阵上二分染色,相邻的不能共处,加一些不能存在的边跑最大独立集以及带权最大独立集.
3. 对于无向图可以构造两条边,dinic不担心本身有环.
4. 如果点有最大流限制,拆点加限制.并且定义好出入.如果点有最小流限制e,最大限制c,把点u拆成ua(负责接收 入) ub(负责放出 出). 在原本的s和t的基础上再搞S和T,然后,S连ub,流e, ua连T,流e. 超级S和T接收总流量就行.(因为有新的附加,在S-s,t-T跑之前,先跑一遍S和T,看有没有达到e和,防止附加值下限不对).
5. 增加流和推流,增加流找到相应的增加就行.但是推流:u->v,如果cap>flow,那么直接cap–,如果cap==flow,那么从v遍历到t,找flow>0的路,退一个流.从s找到u,一样.然后最大流–;其实也可以不用退:如果u->v找到一个增广路,就不用退,跑一个1流就行.
6. 对时间的拆点和二分思路:图中有很多个门,每个门一秒只能出去一个人.然后问图中的人均出去的最短时间.到门的分配问题,涉及到最短时间.二分枚举最短时间,然后判定.一个门一秒只能流一个人,门的t拆点.
最大权闭合图.可能需要自己先构造虚拟点,比如太空做实验的题目.正值的点放在s连,并且求一个sum,负值的点放在t,改为正值加边.然后中间有关系的不能断的搞INF.跑一遍最大流.
最小割可以用来求将图割成两块的边权.随便搞一个点最为s,枚举另一个点,求一下割.所有的最小值就是最小值.但是这样复杂度太高.可以用Stoer-Wagner算法.
最大稠密子图.在一个无向图中,找出一部分图:使得边的个数/点的个数 最大. 首先变成分数规划,g = max(e/v). 然后h(g) = max(e – gv), 是一个单调减.且答案是让h(g)=0,的g. 因此二分枚举g,然后搞出max(e-gv), 转化一下:ans = -min(g – (dv/2 – C(v,v’))). 然后ans = -min(g-dv/2 + C(v,v’)), 括号内的构图: S连每个点权值为U(很大的数,这里为它的度);相连的点关系不变,权值为1;每个点连T,权值为U+2*g-dv. 然后ans = U*n – dinic;比如下面的代码:
include
include
include
include
include
include
include
include
include
include
define LL long long
define _LL __int64
define eps 1e-8
using namespace std;const int INF = 0x3f3f3f3f;
const int maxn = 110;
const int maxm = 1010;
struct node
{
int u,v;
double w;
int next,rev;
}p[maxm],edge[maxm*6];
int s,t,n,m,nn;
int cnt,head[maxn];
int d[maxn];
int dist[maxn],vis[maxn];
int ans;
void init()
{
cnt = 0;
memset(head,-1,sizeof(head));
}
void add(int u, int v, double w)
{
edge[cnt] = (struct node){u,v,w,head[u],cnt+1};
head[u] = cnt++;
edge[cnt] = (struct node){v,u,0,head[v],cnt-1}; head[v] = cnt++;
}
void build(double mid)
{
init();
for(int i = 1; i <= m; i++)
{
add(p[i].u,p[i].v,1.0);
add(p[i].v,p[i].u,1.0);
}
for(int i = 1; i <= nn; i++)
{
add(s,i,m);
add(i,t,m*1.0+2*mid-d[i]*1.0);
}
}
bool bfs()
{
queue que;
memset(dist, 0, sizeof(dist));
memset(vis, 0, sizeof(vis));
while(!que.empty()) que.pop();
vis[s] = 1;
que.push(s);
while(!que.empty())
{
int u = que.front();
que.pop();
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].v;
if(edge[i].w && !vis[v])
{
que.push(v);
vis[v] = 1;
dist[v] = dist[u]+1;
}
}
}
if(dist[t] == 0)
return false;
return true;
}
double dfs(int u, double delta)
{
if(u == t) return delta;
double ret = 0,tmp;
for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].v; if(edge[i].w && dist[edge[i].v] == dist[u]+1 && (tmp = dfs(v,min(delta,edge[i].w)))) { edge[i].w -= tmp; edge[edge[i].rev].w += tmp; return tmp; } } if(!ret) dist[u] = -1; return ret;
}
double Dinic()
{
double ans = 0,res;
while(bfs())
{
while(res = dfs(s,INF))
ans += res;
}
return ans;
}
void dfs_cut(int u)
{
vis[u] = 1;
if(u <= nn) ans++;
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].v;
if(edge[i].w > 0 && !vis[v])
{
dfs_cut(v);
}
}
}
int main()
{
while(~scanf(“%d %d”,&n,&m))
{
if(m == 0)
{
printf(“1\n1\n”);
continue;
}
memset(d,0,sizeof(d));
for(int i = 1; i <= m; i++)
{
scanf(“%d %d”,&p[i].u,&p[i].v);
d[p[i].u]++;
d[p[i].v]++;
}
s = n+1; t = n+2; nn = n; n = t; double low,high,mid,h; low = 0.0; high = m; while(high - low > 1.0/nn/nn) //其实随便定义一个精度就好 { mid = (high + low)/2; build(mid); 箭头建图 h = (m*nn*1.0 - Dinic() )/2; //m就是U,/2是因为人为扩充了. if(h > eps) //单调递减 low = mid; else high = mid; } build(low); //再跑一次,要构造答案. Dinic(); ans = 0; memset(vis,0,sizeof(vis)); dfs_cut(s); printf("%d\n",ans); for(int i = 1; i <= nn; i++) { if(vis[i]) printf("%d\n",i); } } return 0;
}
有边权:求的是子图的边权和除以点数最大。此时U为所有边权的和,中间相连的点权值为we,连T权值为U+2*g-dw(dw为所有连v的边权和)
有点权和边权:求的是子图的边权和和点权和除以点数最大。此时U为所有边权和加二倍点权的和,中间相连的点权值为we,连T的权值为U+2*(g-pv)-dw
二. 二分图匹配
1. 模板(不一定有dinic快的模板……上限VE)
///max_v, V, get_G, 0 ~ V - 1;
//v*E;
const int MAXN = 2e4 + 100;
const int INF = INT_MAX/2 - 1;
vector G[MAXN];
int V;
int m, n;
int match[MAXN], used[MAXN];
void add_edge(int from, int to)
{
G[from].push_back(to);
G[to].push_back(from);
}
int dfs(int v)
{
used[v] = 1;
for(int i = 0; i < G[v].size(); i++)
{
int u = G[v][i], w = match[u];
if(w < 0 || (!used[w] && dfs(w)))
{
match[u] = v;
match[v] = u;
return 1;
}
}
return 0;
}
int b_match()
{
int res = 0;
fill(match, match + V, -1);
for(int i = 0; i < V; i++)//其实到m就行了.
{
if(match[i] < 0)
{
CLR(used);
if(dfs(i))
{
res++;
}
}
}
return res;
}
/*
//可以找出是那些点覆盖了边.
//方法:x集合中未匹配的大胆表used, 然后与之相连的y集合中的要标成used,之后只有与y相匹配的x中的点才敢标成used,再深搜次x,最终x集合中未标记的和y集合中标记//的选出来.
*/
void find_dfs(int v)
{
used[v] = 1;
REP(i, 0, G[v].size())
{
if(!used[G[v][i]])
{
used[G[v][i]] = 1;
int new_v = match[G[v][i]];
if(new_v != -1)
{
find_dfs(new_v);
}
}
}
}
void find_who()
{
CLR(used);
REP(i, 0, n)
{
if(match[i] == -1 && !used[i])
{
find_dfs(i);
}
}
REP(i, 0, n)
{
if(!used[i])//不是多余的
{
printf(” r%d”, i + 1);
}
}
REP(i, n, n + m)
{
if(used[i])//右边集合要有的.
{
printf(” c%d”, i - n + 1);
}
}
printf(“\n”);
}
2. KM算法跑边带权匹配(会将x节点找到匹配,y节点可以多 n^3)
//nx, ny, w[i][j] 有就写,没有就弄一个特别大的,使得结果可以判定.
//可能要改成double的:w, lx, ly, slack, t, t与0的比较, d;
const int maxn = 200;
const int INF = 1e6;
int w[maxn][maxn];
int lx[maxn], ly[maxn];
int linky[maxn];
int visx[maxn], visy[maxn];
int slack[maxn];
int nx, ny;
int find(int x)
{
visx[x] = 1;
for(int y = 0;y < ny;y++)
{
if(visy[y])
continue;
int t = lx[x] + ly[y] - w[x][y];
if(t == 0)
{
visy[y] = 1;
if(linky[y] == -1 || find(linky[y]))
{
linky[y] = x;
return 1;
}
}
else if(slack[y] > t)
{
slack[y] = t;
}
}
return 0;
}
int KM()
{
memset(linky, -1, sizeof(linky));
CLR(ly);
for(int i = 0;i < nx;i++)
{
lx[i] = -INF;
for(int j = 0;j < ny;j++)
{
if(w[i][j] > lx[i])
{
lx[i] = w[i][j];
}
}
}
for(int x = 0;x < nx;x++)
{
for(int i = 0;i < ny;i++)
slack[i] = INF;
while(1)
{
CLR(visx);
CLR(visy);
if(find(x))
{
break;
}
int d = INF;
for(int i = 0;i < ny;i++)
{
if(!visy[i] && d > slack[i])
d = slack[i];
}
for(int i = 0;i < nx;i++)
{
if(visx[i])
lx[i] -= d;
}
for(int i = 0;i < ny;i++)
{
if(visy[i])
{
ly[i] += d;
}
else
{
slack[i] -= d;
}
}
}
}
int res = 0;
for(int i = 0;i < ny;i++)
{
if(linky[i] > -1)
{
res += w[linky[i]][i];
}
} return res;
}
最小边覆盖点 = V-匹配. 边带权值的话:如果是二分图:KM算法. 或者是用网络流,左边要建2*n个点,右边2*m个点,因为每一个点并不是流为1,但是我们要统计流为1,这样总和才对.
最小顶点覆盖边. 二分图就等于最大匹配数. 带权的点:用最小割来解决.u->v,那么割u或者割v.和最大权独立集构造方法一样(本身就是互反的).
最大独立集.应用很广泛,不能有边的最大点的个数.总的点数-最大匹配数. 如果带权:经典的构造出来二分图,然后总和减去割.
最大独立集的思维转换.有男生和女生,有的认识.选择一个最大的集合,使得这里面的男生和女生都认识.反过来看,就是不能出现两个不认识的人.那么把不认识的人建边,找最大独立集. 逆向思维.
矩阵网格,格子里有东西,然后行和列什么的,以行和列分成二分图,格子的东西为边.
矩阵网格还经常二分图染色.比如相邻的不能存在,那么就是二分图独立集.相邻的1*2的一块涨分,那么染色后分供应的和被动接受的.
对时间的拆点和二分思路:图中有很多个门,每个门一秒只能出去一个人.然后问图中的人均出去的最短时间.到门的分配问题,涉及到最短时间.二分枚举最短时间,然后判定.一个门一秒只能流一个人,门的t拆点.
牛必须吃认定的饮料和蔬菜.每个饮料和蔬菜都有限制.问同时满足的牛个数:两个一起供应到的问题,不能用匹配.两个刚好可以饮料+牛拆a+牛拆b+蔬菜来建图.
一个原件i在A加工和在B加工代价不一样.一共n个,ij可能有连接关系,不在一块有额外的费用. 用最小割来描述很合适:sB iA t,然后如果i和j有联系,在i和j之间连一条边w. 注意,最大流都是有向流,对于i和j的边应该建两条.
n玩具在工厂里制作,有玩具需要等,求玩具等待的总时间.只有一个工厂时,是贪心,有很多工厂时,考虑每一个玩具对答案的贡献,发现对每一个玩具弄一个这样的匹配:对每一个工厂建立n个点,表示倒数第几个在工厂里将,一个点用一次,边为x*cost,然后求最小匹配.
一堆x,一堆y,给出可能配对的信息,问为了保证配对最多.一定能够确定的对数是多少.其实就是看边是不是关建边.先跑一遍二分图匹配,然后扫x集合,找到对应的y,把x和y的match都清除,边的关系也暂时清除,直接dfs(x)找增广路,能找到,说明不是关键边,之后把边补上去就行,不用还原match,如果找不到,那么要还原match,并且补上边,而且是关建边.
注意从一般图中看出来是二分图.二分图:如果分成x和y集合,在x或者y集合内部不可能存在边的就是二分图.就是说x和y有关系,y和z有关系,那么z一定和x是一个集合,并且z和x一定不能发生关系.
矩阵上的消除某些格子,把某一个格子当成边,两端的顶点往行和列上靠然后往最小点覆盖边上靠.比如:矩阵上有草地和泥,现在用1*x的木板来盖泥,不能盖草地,那么最少需要多少块木板.想象泥是一条边,那么用木板来盖它,行的连续的木板放在x集合,列的连续的木板放在Y集合.这样每一个泥坑都连一条边,之后求最小点覆盖边.
一个人可以往x走,也可以往y走,但只能选一个地方的感觉.一共有l个地方,每两个地方都有时间消耗,要求ti时刻有人在i点,至少需要几个人.一个人的话,从i走到j和k都可能,到底走到哪?其实在整个图上看是走到哪可以连接最多的边.每连一条边就能够减小1个人.所以拆点成二分图然后匹配.
最小k路径覆盖.每次只能走简单路,最少多少条能够覆盖.每个点拆成两个点,一个出,一个接收(入).然后每匹配一个相当于减少一次.魔术球问题:反着问,k条路可以走多少个点.二分枚举点,看<=k否. 于是二分枚举可以做,暴力枚举然后跑残余的二分图也可以做.其实这个二分图也可以建最大流.s向左边的每一个点(出)都搞一个1的流,右边(入)都向t搞一个1的流,然后左边可以留到右边的搞一个INF的流(因为路径可以重复).然后跑最大流.
搞多匹配问题:供应多个,接收接收多个.然后最大流是不是满足.加上费用就是花费最少了.
多线程过程中一个数的使用唯一性限定.最长递增子序列,一个数只能用一次.有几个同时最长的序列.一个数用一次,可以拆点限定.然后需要描述:走通一条流就是一个递增子序列.那么先跑n^2的f[i],只有i到j连边只有i结尾的通过上上了j然后f[j]=f[i]+1.并且s只和f[i]=1的连,f[i]=max_len的和t连.保证一条路一定是对的. 注意:如果允许某些点同时用,不能简单的放拆点的是INF.因为还涉及到和s和t的关系,到底放不成INF?
最小费用流
模板(spfa跑, F*(kn))
//get_G; V; const int MAXN = 1000 + 10;
const int INF = INT_MAX/2 - 1;
struct edge
{
int to, cap, cost, rev;
edge(int a = 0, int b = 0, int c = 0, int d = 0)
{
to = a;
cap = b;
cost = c;
rev = d;
}
};
vector G[MAXN];
int V;
void add_edge(int from, int to, int cap, int cost)
{
G[from].push_back(edge(to, cap, cost, G[to].size()));
G[to].push_back(edge(from, 0, -cost, G[from].size() - 1));
}
int dist[MAXN], inq[MAXN];
int prev_v[MAXN], prev_i[MAXN];
int spfa(int s, int t)
{
queue myq;
for(int i = 0;i < V;i++)
{
dist[i] = INF;
}
CLR(inq);
dist[s] = 0;
inq[s] = 1;
myq.push(s);
while(!myq.empty())
{
int v = myq.front();inq[v] = 0; myq.pop();
for(unsigned int i = 0;i < G[v].size();i++)
{
edge &e = G[v][i];
if(e.cap > 0 && dist[v] + e.cost < dist[e.to])
{
dist[e.to] = dist[v] + e.cost;
prev_v[e.to] = v;
prev_i[e.to] = i;
if(inq[e.to] == 0)
{
inq[e.to] = 1;
myq.push(e.to);
}
}
}
}
if(dist[t] == INF)
{
return 0;
}
else
{
return 1;
}
}
int min_cost_flow(int s, int t, int f)
{
int res = 0;
while(f > 0)
{
if(spfa(s, t) == 0)
{
return -1;
}
else
{
int d = f;
for(int v = t;v != s;v = prev_v[v])
{
d = min(d, G[prev_v[v]][prev_i[v]].cap);
}
f -= d;
res += d*dist[t];
for(int v = t;v != s;v = prev_v[v])
{
edge &e = G[prev_v[v]][prev_i[v]];
e.cap -= d;
G[e.to][e.rev].cap += d;
}
}
}
return res;
}
这个不能跑环.因为一旦有环一定会出现负环.所以要先分出来二分图再跑费用流.
对于给出的一个已经跑出来最小费用的图,如何判断是在f流下的真正最小费用?就是在残余图上跑cap>0(有效边),然后看有没有负环(spfa跑不出来).于是有一种查负环方法:用q_time记数,一旦超了,那么u返回.然后用pre_v一直找到一个真正的环.查出来之后,绕着环跑一个流就好.
一般只能跑有向图.如果有向图中边有最少流量要求.那么可以这样做:u->v,最少e,最大f,u到v建 cost-M(足够大) 一共e条流,然后f-e条cost的正常流. 最后的花费+e*M就好.
如果有向图中的花费有负边,那么本来是不能搞的,但是通过下面转换:如果最大流F一定,那么新建S和T,超级,往s和t流F,然后对于u->v,流量f,cost<0的情况,S先连到v(f,0),u先连到T(f,0),之后从v向u连-cost,(-cost是正值).最终结果在上cost(负值)*f.
多线程问题.从s走到t,再走回来,不能重复.一下跑流量为2的流. 还有最大K区间覆盖问题.分别取尝试覆盖k次,并且用到的区间边不能重复,因此跑一个k的流.特别的,对于k区间覆盖,两种方法:如果离散的点,区间作为边来跑. 或者:每个区间当做点,拆两个点,然后排序后给能够在后面取的均连一条边.
最大k覆盖的一个应用.题目:一共有10个桶,有n~1e4个球,编号ai,有一个k~1e4的序列,每次放相应编号的球,如果已经有了,不用放,没有的话挑一个免费挤跑,然后放进去,放的时候需要花wi的钱.问放的费用最小.一共有10个桶,意思就是同时不能有大于10的流出现,假设每个球都花钱,看哪些能省,相同的连一个区间,可以省,这些区间中跑最大10覆盖.就是可以省的最大权值.注意,i和i+1用的球是一个的时候,并不会占用一个桶也可以省,因此在最初的时候要消去这种情况.哈哈哈自己想到的.
如果不是流量*边费用,而是边费用的和,可能是最小树形图
纸巾.就是i天的可能给j天用.i天有要求流到t ai,但是i天同时又给其他天提供了ai的流,怎么办?拆点,右端的搞接收,然后传给t,左端的给出,并且已知左端一定会给出总的是ai的流(而且是免费获得).注意i->i+1建有向边减少总的边数.
费用流按照时间来拆点分层.注意t->t+1的无条件联通.还可能按照剩余的油值来分层,可能直接跑最短路就成了.
费用流解决多线程问题,通过拆点限制流可以限制点的使用次数.
如果将原DAG权值取反,然后从最后一关连一条正权边到第一关,权值是最短路(负权值最短路=传统意义上的最长路)的长度的话,那么那些正圈中的负权边就是应该增加权值的边,具体应该加多少,就是正圈的权值。
再看怎么用网络流计算这些正圈权值,新建源点汇点,对于所有顶点,如果入度>出度,从源点连一条边到它,否则,从它连一条边到汇点,容量都是是度数差。
还没弄懂: http://www.hankcs.com/program/algorithm/aoj-2230-how-to-create-a-good-game.html
相关文章推荐
- 【工业串口和网络软件通讯平台(SuperIO)教程】二.架构和组成部分
- OSI七层网络参考模型
- 【工业串口和网络软件通讯平台(SuperIO)教程】二.架构和组成部分
- HTTP 笔记与总结(9)分块传输、持久链接 与 反向 ajax(comet / server push / 服务器推技术)
- 理解HTTP幂等性(转)
- qt程序运行时的错误?undefined reference to `_imp___ZN10QTcpSocketD1Ev'
- (网络视频监控)面试题12
- Java网络编程之TCP通信
- http://www.zhihuishi.com/source/1.html
- 云计算和大数据时代网络技术揭秘(十一)数据中心互联
- yuv测试序列视频下载网址 http://media.xiph.org/video/derf/
- http:get&post方法
- 卷积神经网络误差分析
- UVA 1660 Cable TV Network 电视网络(无向图,点连通度,最大流)
- Node学习HTTP模块(HTTP 服务器与客户端)
- 网络协议概述:物理层、连接层、网络层、传输层、应用层详解(转载)
- Mac 启用http-dav功能(WebDAV服务器)
- AngularJS 用 $http.jsonp 方法跨域请求数据错误的问题
- ZOJ 3792 - Romantic Value (网络流‘最小割)
- [BZOJ1834][ZJOI2010]network 网络扩容