比赛从3.3日开始,但是其实3.2日赛题已经出来了,那天下午在实验室同学的提醒下就下载了赛题,然后花了好久才看懂是什么东西。开始真是一头雾水,看群里有人说是最大流问题,然后就想到看的算法导论里面第26章,然后就自然想到了最小费用最大流问题,也算是幸运,在这个上面没花什么时间。
3.2日晚上想了好久,想到了一个思路,就是把所有消费节点直连的网络节点都放置服务器,然后再删服务器,然后像中间靠拢,后面可以尝试一些启发式。然后就嗨了,,,结果第二天小原的介绍赛题的视频里面直接就说了直连,让我感觉前天晚上白想了,真是笑哭了。
有了思路后就是撸代码了,花了一点时间把项目转到vs上面,然后读取数据,直连,我的第一版就这么产生了,从5号晚上开始,花了整整一天时间来上传,才成功,原因是跟去年的不一样,去年直接编译直接提交压缩包就行了,今年得自己压缩,,,真是心累啊!
当时还是西北赛区第一个提交成功的人,然后也保持了很久的第一,这个还是让我比较开心的一件事,至少我撸代码速度还是比较快的。但是从第一开始往后掉的感受真心虐。
这里有个小插曲,开始的时候官网的评分机制是有问题的,给出来的case都比较小,我用的同学的一个小号程序员鼓励师就这么在第一的位置持续了十来天左右,兰大的哥们应该到现在也不知道他们前面的第一是个小号吧!不过这也是让兰大的哥们一直优化的动力啊,哈哈。
有了直连版本,后面的就好说了,费用流开始用的是SPFA,对开始的小case,还是可以满足的,然后就写了删点,相邻结点交换,度最高的点交换(度定义的是每个节点输出带宽/带宽费用),然后就靠这个称了几天,直到8号左右开始第一保不住了,然后就慌了,觉得必须得上点启发式了,然后就花了两天时间改了我的第一版遗传。
第一版遗传的效果特别渣,可以说根本就没什么效果,有的case都没有直连效果好,现在时间有点长了,具体bug忘记了,但是当时记得是一个很搞笑的bug。
后来很长一段时间都没有什么进展,然后到了28号左右的时候,SPFA已经明显跟不上节奏了,必须得换其他算法了,就花了两天时间改了ZKW算法,这个算法真是厉害,因为里面里面包含了一个反向边的概念,之前一直没有搞懂这个,所有一直没有看懂,所以也一直没有用,换了zkw之后,遗传对中小case可以达到最优了,但是对高级还是不行,因为我的初始解太大了(初赛的初始解都是随机的,现在想想能撑到复赛也是心累),高级我用的还是最初的那个删点+相邻结点交换+度高的交换。
最后最终case出来的时候,我还能保持在十来名左右,所以也就有点放松心态了,没怎么优化了,但是最后一天直接从14名掉到33名,真吓尿了,然后又花了点时间优化下提交了,刷到前面去了,最后初赛结束,保住了32强,最后是31名,现在想想真是惊险。
最后总结一下,这个比赛真心累,但是也会让人上瘾,因为花一个月时间认真去干一件事情确实收获很大,比赛过程中确实也学到了很多东西。
发现当时比赛的时候有好多话想说,结果拖到现在全忘了,就这样吧,哈哈!
初赛代码还有很多问题,比如遗传交叉其实作用不大,但是我每次遗传的时候交叉计算一次cost,然后变异计算一次cost,浪费了大把时间;
然后初始解也有问题,我遗传的初始解是随机的,这样的话初始解cost就很大,所以当时高级case一直降不下去就是因为这个原因,在复赛的时候解决了
update函数是开始给一个直连解,然后删服务器,度比较好的交换,相邻结点交换,都是贪心的,
#include "deploy.h" #include <stdio.h> #include <memory.h> #include <stdlib.h> #include <time.h> #include <math.h> #include <queue> #include "timerclock.h" #include <vector> #include <iostream> using namespace std; std::time_t Timer::_time_start; #define SUM 6 //总共的染色体数量 #define MAXLOOP 10000 //遗传最大循环次数 #define OUTLOOP 10000 //多少次最优解不变退出 #define maxcrossp 0.8 //交叉概率 #define maxmp 0.9 //变异概率 #define mincrossp 0.3 //最小交叉概率 #define minmp 0.00001 //最小变异概率 #define NUMEDGE 30000 //原图的最大边数 const int MAXN = 1510; const int MAXM = 40000000; const int INF = 1000000000; const int S = 1501; const int T = 1502; int result_unchange_time; //记录在error前提下最优值未改变的循环次数 int NE; //边的数量 int head[MAXN]; //以某一个结点为起头的是那一条边 int dist[MAXN]; //存储到源点s的距离 int pp[MAXN]; //存储前驱顶点 int numserver = 0; //服务器数量 int numedge = 0; //边数 int numnode = 0; //结点数 int numcons = 0; //消费结点数 int servercost = 0; //安装每台服务器花费 //int path[1000][100]; //输出路径时记录路径 char ret[40000]; //保存输出字符串 bool vis[MAXN]; //访问标记 int MIN_COST; int relasum[1000]; //按出度分割的数组,用于初始化解 vector<vector<int>> retpath; vector<int > tempway; vector<int > costway; /*******************************结点的度排序结构**************************************/ struct node{// float sum; //每个网络节点的单位价钱出度 bool havedep;//是否放置了服务器 }point[MAXN]; /*******************************图的邻接表表示****************************************/ struct Edge { int u; int v; int cap; int cost; int next; }edge[NUMEDGE],incomedge[MAXM]; /***************************************基因结构体************************************/ struct gen{ bool info[1000]; //染色体结构,用一个数组作为染色体编码 int cost; //此染色体结构的花费 float fitfx; }; struct gen gen_group[SUM]; //定义一个含有20个染色体的组 struct gen gen_new[SUM]; //交叉变异产生的新染色体 struct gen gen_result; //记录最优的染色体 /**************************************基础函数声明**********************************/ void recover(); void init(); void ways(int f,int u); void reading(char * topo[MAX_EDGE_NUM]); //读取数据 void addedge(int u,int v,int cap,int cost); //个位的0、1代表一对正反向的边 void deledge(int u,int v); //个位的0、1代表一对正反向的边 void character(int temp,int& i); //输出数字转换成char数组 int getcost(int ans); char* output(); int relativerand(int *div ,int left, int right); //按相对权值返回一个随机值 bool insertserver(int i); bool isright(); int randbit(int i,int j); //产生一个在i,j两个数之间的随机整数 int randsign(float p); //按概率p返回1 void delser(int aaa); /**************************************遗传操作声明**********************************/ void initiate(); //初始化函数,主要负责产生初始化种群 void delser2(); //删除初始解中没有用的网络节点 void optimi( int flag ); //评估种群中各染色体的适应度,并据此进行排序 void gen_swap(gen *a,gen *b); //结构体交换 void gen_quicksort(gen *number,int left,int right); //种群排序 void cross(int n); //交叉函数 void selection(); //轮盘赌选择函数 void selection1(); //快速选择函数 void mutation(int n); //变异函数 int record(int n); //记录每次循环产生的最优解并判断是否终止循环 int ANS,TEMPCOST; //返回的总cost int aug (int u,int f) { if (u == T) { ANS+=TEMPCOST*f; return f; } vis[u]=true; int tmp=f; for (int i=head[u]; i!=-1; i=incomedge[i].next){ if (incomedge[i].cap && !incomedge[i].cost && !vis[incomedge[i].v]) { int delta = aug(incomedge[i].v, tmp < incomedge[i].cap ? tmp : incomedge[i].cap); incomedge[i].cap -= delta; incomedge[i^1].cap += delta; tmp -= delta; if (!tmp) return f; } } return f-tmp; } bool modlabel() { for(int i = 0; i < MAXN; i++) dist[i] = INF; dist[T] = 0; deque<int>Q; Q.push_back(T); while(!Q.empty()) { int u = Q.front(), tmp; Q.pop_front(); for(int i = head[u]; i != -1; i = incomedge[i].next) if(incomedge[i^1].cap && (tmp = dist[u] - incomedge[i].cost) < dist[incomedge[i].v]) (dist[incomedge[i].v] = tmp) <= dist[Q.empty() ? S : Q.front()] ? Q.push_front(incomedge[i].v) : Q.push_back(incomedge[i].v); } for(int u = 0; u < MAXN; u++) for(int i = head[u]; i != -1; i = incomedge[i].next) incomedge[i].cost += dist[incomedge[i].v] - dist[u]; TEMPCOST += dist[S]; return dist[S] < INF; } void ZKW() { ANS=TEMPCOST=0; while(modlabel()) { do { memset(vis, 0, sizeof(vis)); }while(aug(S, INF)); } } bool Update3(){ int cost=MIN_COST; //删除某一服务器,是否可行 int jj=head[T]; while(jj!=-1&& !Timer::timeout()){ deledge(T,incomedge[jj].v); numserver--; recover(); ZKW(); int coss=getcost(ANS); if(isright()&&coss<MIN_COST){ MIN_COST=coss; point[incomedge[jj].v].havedep=false; } else{ addedge(incomedge[jj].v,T,INF,0); numserver++; } jj=incomedge[jj].next; recover(); } //度排序后从大到小替换 jj=head[T]; while(jj!=-1&& !Timer::timeout()){ int dey=0; while(point[dey].havedep){ ++dey; } for(int i=dey+1;i<numnode;++i){ if(point[i].havedep==false&&point[i].sum>point[dey].sum){ dey=i; } } deledge(T,incomedge[jj].v); addedge(dey,T,INF,0); recover(); ZKW(); int coss=getcost(ANS); if(isright()&&coss<MIN_COST){ MIN_COST=coss; point[dey].havedep=true; point[incomedge[jj].v].havedep=false; jj=incomedge[jj].next; continue; } else{ deledge(T,dey); addedge(incomedge[jj].v,T,INF,0); recover(); } jj=incomedge[jj].next; } //邻域替换 int ptredge=head[T]; while(ptredge!=-1&& !Timer::timeout()){ int nextedge=head[incomedge[ptredge].v]; while(nextedge!=-1){ if(incomedge[nextedge].v!=T){ deledge(T,incomedge[ptredge].v); addedge(incomedge[nextedge].v,T,INF,0); recover(); ZKW(); int coss=getcost(ANS); if(isright()&&coss<MIN_COST){ MIN_COST=coss; point[incomedge[nextedge].v].havedep=true; point[incomedge[ptredge].v].havedep=false; break; } else{ deledge(T,incomedge[nextedge].v); addedge(incomedge[ptredge].v,T,INF,0); recover(); } } nextedge=incomedge[nextedge].next; } ptredge=incomedge[ptredge].next; } if(cost>MIN_COST)return true; else return false; } void deploy_server(char * topo[MAX_EDGE_NUM], int line_num,char * filename){ Timer::tic(); int i; result_unchange_time = 0; gen_result.cost = INF; init(); reading(topo); if(numnode<600){ srand((unsigned)time(NULL)); //srand(1); initiate(); //产生初始化种群,未经优化的初始种群 optimi( 0 ); //对父种群进行优化,评分,排序 MIN_COST = INF; for(i = 0; i < MAXLOOP && !Timer::timeout(); ++i){ cross(i); optimi(1); if(record(i) == 1){ break; } selection(); mutation(i); optimi(0); if(gen_group[0].cost > gen_result.cost){ //精英保留 gen_group[SUM-1].cost = gen_result.cost; for(int k = 0; k < numnode ; ++k){ gen_group[SUM-1].info[k] = gen_result.info[k]; } } } printf("迭代次数:%d ",i); int ptr = head[T]; while(ptr != -1){ deledge( T , incomedge[ptr].v ); ptr = incomedge[ptr].next; } numserver=0; //把最优的染色体染色体中的服务器装配上 for(int z=0;z<numnode;++z){ if(gen_result.info[z]==1){ addedge(z,T,INF,0); numserver++; } } }else{ numserver=numcons; MIN_COST=INF; int loop = 6; while(loop>=0){ Update3(); loop--; } } recover(); ZKW(); i = head[S]; while(i != -1){ ways(edge[i].cap , edge[i].v); i = edge[i].next; } char *topo_file=output(); //printf("%s ",topo_file); printf("zkw计算总花费为:%d ",getcost(ANS)); write_result(topo_file, filename); }void selection(){ //轮盘赌 int i , j , k; int min = INF, max = 0; for(i = 0; i < SUM; ++i){ if(min>gen_group[i].cost){ min = gen_group[i].cost; } else if(max < gen_group[i].cost && gen_group[i].cost != INF){ max = gen_group[i].cost; } if(min>gen_new[i].cost){ min = gen_new[i].cost; } else if(max < gen_new[i].cost && gen_new[i].cost != INF){ max = gen_new[i].cost; } } float diff = (float)max - min; for(i = 0; i < SUM; ++i){ if(gen_group[i].cost == INF){ gen_group[i].fitfx = 0; } else{ float temp = (gen_group[i].cost - min + diff)/diff; gen_group[i].fitfx = 1.0/(pow(temp,13)); } if(gen_new[i].cost == INF){ gen_new[i].fitfx = 0; } else{ float temp = (gen_new[i].cost - min + diff)/diff; gen_new[i].fitfx = 1.0/(pow(temp,13)); } } float sum = 0.0; //适应度函数求和 for(i = 0; i < SUM ; ++i){ sum += gen_group[i].fitfx + gen_new[i].fitfx; } int relfitfx[SUM * 2]; //相对适应度分配 relfitfx[0] = 0; for(i = 1; i < SUM ; ++i){ relfitfx[i] = relfitfx[i-1] + 32768 * gen_group[i].fitfx/sum; } for(i = 0; i < SUM ; ++i){ relfitfx[SUM+i] = relfitfx[SUM+i-1] + 32768 * gen_new[i].fitfx/sum; } bool temp[SUM * 2]; //保存该个体是否选择 memset(temp,false,sizeof(temp)); int loop = 0; i = 0; while(loop < SUM && i<2000){ //如果2000次没有选足下一代,就退出 int tt = relativerand(relfitfx ,0 ,SUM*2-1); if(!temp[tt]){ temp[tt] = true; ++loop; } ++i; } j = 0; for(i = 0;i < SUM*2 -1 ;++i){ if(temp[i]){ if(i < SUM){ gen_group[j].cost = gen_group[i].cost; for(k = 0; k < numnode ; ++k){ gen_group[j].info[k] = gen_group[i].info[k]; } } else{ gen_group[j].cost = gen_new[i-SUM].cost; for(k = 0; k < numnode ; ++k){ gen_group[j].info[k] = gen_new[i-SUM].info[k]; } } ++j; } } } int record(int n){ int i; if( gen_group[0].cost < gen_result.cost && gen_group[0].cost < gen_new[0].cost){ for( i = 0; i < numnode; ++i ){ gen_result.info[i] = gen_group[0].info[i]; } gen_result.cost = gen_group[0].cost; result_unchange_time = 0; } else if(gen_new[0].cost < gen_result.cost){ for( i = 0; i < numnode; ++i ){ gen_result.info[i] = gen_new[0].info[i]; } gen_result.cost = gen_new[0].cost; result_unchange_time = 0; } else{ ++result_unchange_time; if( result_unchange_time >= OUTLOOP ){ return 1; } } return 0; } void mutation(int n){ int i, j; float gmp; gmp = 1 - pow( 1-maxmp , 11);//在基因变异概率为mp时整条染色体的变异概率 for(i = 0;i < SUM;++i){ if( randsign(gmp) == 1 ){//以gmp的概率返回1 j = randbit( 0 , numnode-1 ); if( gen_group[i].info[j] != 1 ){ gen_group[i].info[j] = 1; } else{ gen_group[i].info[j] = 0; } } } } void cross(int n){ int i , j , k ,z; int a[SUM]; for(i = 0 ; i < SUM ; i++) a[i] = 0; //将所有染色体设为没有交叉 k = 0; for( i = 0; i < SUM ; ++i ){ if( a[i] == 0 ){ while( 1 ){ j = randbit( i+1 , SUM - 1 );//随机选与i交叉染色体的位置 if( a[j] == 0 ) break; //如果该点没有被交叉过,就break } if( randsign( maxcrossp ) == 1 ){ //按照crossp的概率对选择的染色体进行交叉操作 int tem=randbit(0 , numnode-1); //随机选交叉点的位置 for( z = 0; z < tem ; ++z){ gen_new[k].info[z] = gen_group[i].info[z]; gen_new[k+1].info[z] = gen_group[j].info[z]; } for( z = tem; z < numnode; ++z){ gen_new[k].info[z] = gen_group[j].info[z]; gen_new[k+1].info[z] = gen_group[i].info[z]; } gen_new[k].cost = INF; gen_new[k+1].cost = INF; } else{ gen_new[k].cost = gen_group[i].cost; gen_new[k+1].cost = gen_group[j].cost; for( z = 0;z < numnode; ++z){ gen_new[k].info[z] = gen_group[i].info[z]; gen_new[k+1].info[z] = gen_group[j].info[z]; } } a[i] = a[j] = 1; k +=2; } } } void optimi( int flag ){ int i, j, ptrhead; struct gen *genp; if( flag == 0) genp = gen_group; else genp = gen_new; for( i = 0; i < SUM; ++i ){ numserver=0; //把改染色体的服务器全连上 for( j = 0; j < numnode; ++j){ if( genp[i].info[j] ){ addedge( j, T, INF, 0 ); ++numserver; } } recover(); ZKW(); if( !isright() ){ //当该染色体不满足消费节点的时候 genp[i].cost = INF; } else{ genp[i].cost = getcost(ANS); } //把所有服务器与汇点断开 ptrhead = head[T]; while( ptrhead != -1 ){ deledge( T , incomedge[ptrhead].v ); //删除与T相连的服务器 ptrhead = incomedge[ptrhead].next; } } gen_quicksort(genp,0,SUM-1); //对种群进行重新排序 } int relativerand(int *div ,int left, int right){ int randtemp = randbit(0 , 32768); int mid; while(left <= right){ mid = (left + right)/2; if(div[mid] < randtemp){ left = mid + 1; } else{ right = mid - 1; } } return right; } void delser2(){ recover(); ZKW(); int ptr = head[T]; while(ptr != -1){ //残缺网络上面该服务器与T之间的带宽没有变,表示该服务器没有使用,则删除 int nptr = head[incomedge[ptr].v]; while(nptr != -1){ if(incomedge[nptr].v == T && incomedge[nptr].cap == INF){ deledge( T, incomedge[ptr].v ); } nptr = incomedge[nptr].next; } ptr = incomedge[ptr].next; } } void initiate(){ int i ,j ,ptrT; //*************初始解0***********************************/ //因为在读取数据是已经把服务器连接到消费节点直接相邻的网络节点上,所以现在直接读取 for(i = 0; i < numnode ; ++i){ gen_group[0].info[i] = false; } gen_group[0].cost = INF; ptrT = head[T]; while(ptrT != -1){ gen_group[0].info[incomedge[ptrT].v] = true; deledge(T , incomedge[ptrT].v); ptrT = incomedge[ptrT].next; } //计算相对出度比例 float sum = 0; relasum[0] = 0; for(i = 0; i < numnode ; ++i){ sum += point[i].sum; } for(i = 1; i < numnode ; ++i){ //计算累计概率 relasum[i] = relasum[i-1] + 32768 * point[i].sum / sum; } //*************初始解1-SUM***********************************/ for(i = 1; i < SUM; ++i ){ for(j = 0; j < numnode ; ++j){ gen_group[i].info[j] = false; } gen_group[i].cost = INF; for(j=0;j<numnode;++j){ point[j].havedep = false; //将所有的点设为没有设服务器 } j = 0; while(j < numcons){ //先随机添加numserver个服务器 int tem = relativerand(relasum ,0 ,numnode - 1); if(!point[tem].havedep){ addedge(tem , T , INF , 0); point[tem].havedep=true; j++; } } recover(); while(!insertserver(relativerand(relasum ,0 ,numnode - 1))){ ; } delser2(); ptrT = head[T]; while(ptrT != -1){ gen_group[i].info[incomedge[ptrT].v] = true; deledge(T , incomedge[ptrT].v); ptrT = incomedge[ptrT].next; } } } bool insertserver(int i){ if(!point[i].havedep){//如果排序后第i小的某个网络节点上没有服务器 addedge(i , T , INF , 0); point[i].havedep=true;//排序后第i小的某个网络节点上布置了服务器 recover(); ZKW(); } return isright(); } void gen_swap(gen *a,gen *b) { gen temp; temp = *a; *a = *b; *b = temp; } void gen_quicksort(gen *number,int left,int right)//快速排序,用于结构体排序 { int i,j,s; if(left<right) { s=number[(left+right)/2].cost,j=right+1,i=left-1; while(1) { while(number[++i].cost<s) ; while(number[--j].cost>s) ; if(i>=j) break; gen_swap(&number[i],&number[j]); } gen_quicksort(number,left,i-1); gen_quicksort(number,j+1,right); } } void reading(char * topo[MAX_EDGE_NUM]){ int temp[3]={0}; int i=0,k=0,j=0; while(topo[0][i]!=' '){ if(topo[0][i]>='0'&&topo[0][i]<='9') temp[k]=temp[k]*10+topo[0][i]-'0'; else{ ++k; } ++i; } numnode=temp[0];numedge=temp[1];numcons=temp[2]; i=0; while(topo[2][i] != ' '&&topo[2][i]!=' '){ servercost=servercost*10+topo[2][i++]-'0'; } int link[4]; for(i=4;i<4+numedge;++i){ k=0;j=0;int tem=0; while(topo[i][k] != ' '&&topo[i][k] != '