题目大意:有n个房子,m条边。编号1的点是网络中心。现在给你k个房子,每个房子都要由一根网线连往网络中心。连线有两个规则:1、房子连到网络中心的一根网线必须完整,只有一个颜色。2、边上的所有网线必须颜色都不一样,边是两个房子之间的边,比如3->2->1就经过了两个边求:最少用多少个颜色。具体看题目。
思路:
肯定是最大流没错,关键还是建图。
网上题解一句话说“颜色的数量取决于网络流中各条边中最大的流量,于是二分边的容量并做网络流就可以了."感觉有点牵强。
首先如果你给每个边的颜色种数是无穷的话,那么你是会得到一个所有边中最大的流量,但是这个流量明显不是我们想要的答案,比如说源点一共有七个流,我们可以从s->1->2->t,或者s->3->4->t,那么就有可能是第一条路过了七个流而第二条路一个没过,其实最少的颜色种数肯定比7小,所以最大流算法只是说有种流法能够保证得到最大流,而不能控制流量具体往哪里流,情况颇多,当然最少颜色种数肯定是某个边上的最大流量。
但我们要做的是压榨每条边上的容量,迫使流改道,想想的话应该是让流更充分的使用容量的情况下更优,那么我们二分答案cap,让每个边的容量为cap,看在这个容量下是不是存在一个方案满足题意,如果有说明这个颜色种数够用,那么我们往下二分,找更小的颜色数;如果不存在即最大流小于k,那么说明颜色数不够,那么就往上调,最后得到一个最少的颜色数。
之前自己的dinic不好用,tle到死,可能是哪里没写好?只好去大牛博客上扒了一份isap的模板,挺好用的,征用了o(∩_∩)o
#include <stdio.h> #include <string.h> #include<iostream> #include <algorithm> #define clear(A, X) memset (A, X, sizeof A) #define copy(A, B) memcpy (A, B, sizeof A) using namespace std; const int maxE=1000000; const int maxN=100000; const int maxQ=1000000; const int oo=0x3f3f3f3f; struct Edge { int v;//弧尾 int c;//容量 int n;//指向下一条从同一个弧头出发的弧 } edge[maxE];//边组 int adj[maxN], cntE;//前向星的表头 int Q[maxQ], head, tail;//队列 int d[maxN], cur[maxN], pre[maxN], num[maxN]; int sourse, sink, nv;//sourse:源点,sink:汇点,nv:编号修改的上限 int n, m,k; int x[maxN],f[maxN],g[maxN]; void addedge (int u, int v, int c)//添加边 { //正向边 edge[cntE].v = v; edge[cntE].c = c;//正向弧的容量为c edge[cntE].n = adj[u]; adj[u] = cntE++; //反向边 edge[cntE].v = u; edge[cntE].c = 0;//反向弧的容量为0 edge[cntE].n = adj[v]; adj[v] = cntE++; } void rev_bfs () //反向BFS标号 { clear (num, 0); clear (d, -1);//没标过号则为-1 d[sink] = 0;//汇点默认为标过号 num[0] = 1; head = tail = 0; Q[tail++] = sink; while (head != tail) { int u = Q[head++]; for (int i = adj[u]; ~i; i = edge[i].n) { int v = edge[i].v; if (~d[v]) continue;//已经标过号 d[v] = d[u] + 1;//标号 Q[tail++] = v; num[d[v]]++; } } } int ISAP() { copy (cur, adj);//复制,当前弧优化 rev_bfs ();//只用标号一次就够了,重标号在ISAP主函数中进行就行了 int flow = 0, u = pre[sourse] = sourse, i; while (d[sink] < nv) //最长也就是一条链,其中最大的标号只会是nv - 1,如果大于等于nv了说明中间已经断层了. { if (u == sink) //如果已经找到了一条增广路,则沿着增广路修改流量 { int f = oo, neck; for (i = sourse; i != sink; i = edge[cur[i]].v) { if (f > edge[cur[i]].c) { f = edge[cur[i]].c;//不断更新需要减少的流量 neck = i;//记录回退点,目的是为了不用再回到起点重新找 } } for (i = sourse; i != sink; i = edge[cur[i]].v) //修改流量 { edge[cur[i]].c -= f; edge[cur[i] ^ 1].c += f; } flow += f;//更新 u = neck;//回退 } for (i = cur[u]; ~i; i = edge[i].n) if (d[edge[i].v] + 1 == d[u] && edge[i].c) break; if (~i) //如果存在可行增广路,更新 { cur[u] = i;//修改当前弧 pre[edge[i].v] = u; u = edge[i].v; } else //否则回退,重新找增广路 { if (0 == (--num[d[u]])) break;//GAP间隙优化,如果出现断层,可以知道一定不会再有增广路了 int mind = nv; for (i = adj[u]; ~i; i = edge[i].n) { if (edge[i].c && mind > d[edge[i].v]) //寻找可以增广的最小标号 { cur[u] = i;//修改当前弧 mind = d[edge[i].v]; } } d[u] = mind + 1; num[d[u]]++; u = pre[u];//回退 } } return flow; } void init () //初始 { clear (adj, -1); cntE = 0; } int main() { int t; scanf("%d",&t); while(t--) { scanf("%d%d%d",&n,&m,&k); sourse=0,sink=1; nv=n+1; for(int i=1; i<=k; i++) { scanf("%d",&x[i]); } for(int i=1; i<=m; i++) { scanf("%d%d",&f[i],&g[i]); } int l=0,r=n; int answer=0; while(l<=r) { init(); for(int i=1; i<=k; i++) { addedge(0,x[i],1);//从0开始到2*k-1 } int mid=(l+r)>>1; for(int i=1; i<=m; i++) { addedge(f[i],g[i],mid); addedge(g[i],f[i],mid); } if(ISAP()==k) { // printf("--------%d\n",mid-1); answer=mid; r=mid-1; } else l=mid+1; } printf("%d\n",answer); } return 0; }
版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/hitwhacmer1
原文地址:http://blog.csdn.net/hitwhacmer1/article/details/48037151