题目大意:有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;
}