我想大部分同学开始时都是直接在与消费节点相邻的网络节点上部署服务器,因为这样至少满足了消费节点的带宽需求。关于如何合理部署服务器我们开始时没有头绪,找了很多关于寻址的论文,里面提到的方法也很多,比如重心法,单纯形法,整数规划,最小费用最大流等等。可惜的是这些方法我都不会。。。我们只好自己想其他办法,开始时用深搜的方法从消费节点开始,找重合度高的网络节点作为服务器的位置,由于比赛初期的用例规模比较小,此方法还比较有用,进入了前10名。但后面随着规模的增大,此方法显然不合适了,我们队伍一度陷入了困境。
有队员就提出了用最小费用流来求解,但由于我们对这些都不理解,初期根本做不出来。但有其他队用该方法得到结果了,我们就下决心把这个搞定。在网上搜索关于费用流的博客研究,学会了建立超级源点和超级汇点,以及用费用流输出路径。关于输出路径真是灵感激发,只要将费用流保存的路径去掉超级源点和超级汇点就是我们所要的路径。求解费用流我们用了spfa和zkw两种方式。得到求解费用的方法后,我们又遇到了回流问题,差点因为这个问题就挂掉。。。
下面附上我们的费用流代码片段zkw:
public static int aug(int u, int f)
{
if(u == des)
{
ans += cost * f;
return f;
}
vis[u] = 1;
int tmp = f;
for(int i = head[u]; i != -1; i = edges[i].next)
if(edges[i].cap>0 && edges[i].cost==0 && vis[edges[i].v]==0)
{
int delta = aug(edges[i].v, tmp < edges[i].cap ? tmp : edges[i].cap);
edges[i].cap -= delta;
edges[edges[i].re].cap += delta;
if(edges[i].init_cost < 0){
flowArrays[edges[i].v][u] = flowArrays[edges[i].v][u] - Math.abs(delta);//后续添加
}else{
flowArrays[u][edges[i].v] = flowArrays[u][edges[i].v] + delta;//后续添加
}
tmp -= delta;
if(tmp<0) return f;
}
return f - tmp;
}
public static boolean modlabel()
{
int delta = INF;
for(int u = 0; u < n; u++)
if(vis[u]>0)
for(int i = head[u]; i != -1; i = edges[i].next)
if(edges[i].cap>0 && vis[edges[i].v]==0 && edges[i].cost < delta) delta = edges[i].cost;
if(delta == INF) return false;
for(int u = 0; u < n; u++)//u=1,u<=n
if(vis[u]>0)
for(int i = head[u]; i != -1; i = edges[i].next){
edges[i].cost -= delta;
edges[edges[i].re].cost += delta;
}
cost += delta;
return true;
}
public static void costflow()
{
do
{
do
{
Arrays.fill(vis, 0);
}while(aug(src, INF)>0);
}while(modlabel());
}
费用流spfa:
public static boolean Spfa(int s, int t){
Arrays.fill(gPre, -1);
Arrays.fill(gDist, INFINITE);
gDist[s] = 0;
Queue<Integer> Q = new LinkedList<Integer>(); // queue<int> Q;
Q.offer(s);//Q.push(s);
while (!Q.isEmpty()){//由于不存在负权和环,因此一定会结束
int u = Q.poll();
for (int e = gHead[u]; e != -1; e = gEdges[e].next){
int v = gEdges[e].to;
if (gEdges[e].vol > 0 && gDist[u] + gEdges[e].cost < gDist[v]){
gDist[v] = gDist[u] + gEdges[e].cost;
gPre[v] = u; //前一个点
gPath[v] = e;//gPath[v] = e;该点连接的前一个边
if(!Q.contains(v)){
Q.offer(v);
}
}
}
}
if (gPre[t] == -1) //若终点t没有设置pre,说明不存在到达终点t的路径
return false;
return true;
}
public static int MinCostFlow(int s, int t){
int cost = 0;
int flow = 0;
while (Spfa(s, t)){
int f = INFINITE;
for (int u = t; u != s; u = gPre[u]){
if (gEdges[gPath[u]].vol < f){
f = gEdges[gPath[u]].vol;
}
}
flow += f;
cost += gDist[t] * f;
Stack<Integer> stk = new Stack<Integer>();
for (int u = t; u != s; u = gPre[u]){
stk.push(u);
gEdges[gPath[u]].vol -= f; //正向边容量减少
gEdges[gPath[u]^1].vol += f; //反向边容量增加
}
return cost;
}