还记得当时做这个比赛的时候好多天都是满脑子想着这道题,除了后面没有意义的玄学调参,参加这种比赛还是很锻炼的。在比赛的过程中需要去学算法读论文这都是一种提高吧。这里写篇文章分析一下当时的思路同时也纪念当初付出的努力吧(虽然没进决赛还是有点遗憾的=_=)
- 题目大意
给一个有向图,有 V (最多600)个节点,每个节点最多连出去8条边,边上有一个权值w,同时给定最多50个
V′ 节点,给定起点S和终点T,让找出一条从起点到终点的经过所有 V′ 点的路径,并且要求路径的总权值尽量小。
一开始拿着这个题感觉就是加了个约束条件的TSP(旅行商)问题,不加条件的旅行商就是一个NP问题了,更何况还需要满足经过一些必经点。高中做过一个用动态规划来解TSP的,但点数很少,复杂度是O(
2n∗n2
).所以不能用ACM考的一些最优化算法来解决。于是就往优化算法方向考虑,蚁群遗传之类。想了遗传算法很久还是没有好的解决办法,遗传算法的一般思路是随机生成n个个体(这道题中的体现是生成一组点的序列也就是一条从起点到终点的路径),这n个个体被称作种群。接下来是对优等个体进行交叉互换,对个体的优劣评估是很容易的,经过
V′
点的个数以及路径的总权值。但主要问题就出在交叉互换上,不管用什么方法交叉互换都很难保证互换后的个体是一条满足条件的路径,也就是说很难把父代的优良性传到子代。最终还是放弃了这个方法,不过我看见还是有人在用这个思想做,结果貌似不是太好。
然后用dijkstra+DFS乱搞结果掺不忍睹分数少的可怜(满分100只有十多分)。又弃之。
最后还是好好想想算法,一开始是没有想到的蚁群算法,毕竟对蚁群也不是很熟,只是之前在类似科普书之类的地方(书名叫《失控》,很不错的书)看见过蚁群的思想。大量简单个体之间通过一些规则涌现出智能的特性。
比如一群蚂蚁要将一些东西在A B间来回搬,蚂蚁有一些行为规则,在沿途上会留下一些信息素,并且会有比较大的路径走信息素多的路径。这样,在相同时间内那些更短的路径被走的次数会比长的路径更多,所以会积累更多的信息素(密度),另一个规则是信息素多的路径会有比较大的概率被其它蚂蚁走。还有信息素的挥发,那些不是很好的路径慢慢地信息素会挥发掉从而更小的概率走。在这些规则下在经过大量的迭代后一条比较接近最优的路径会涌现出来。
而我一开始想的是用一个基于概率的模型来表示图,对于一个点连出去的边中,每条边都有一个概率值表示这条边在这些边中被走的概率有多大(这个概率值就类似于信息素的作用,但我没加入挥发的功能,一是因为这样写会更加麻烦,还有就是使得调参也变得更为困难)这个模型建好了后从图中根据边的概率走,直到走出一条从起点到终点的路径,先不保证经过所有的
V′
点 。在走出很多条边后对这些边按照优劣排序,将排名靠前的边增加这些边被走的概率。这样其实是相当于一个简化版的蚁群算法。分数大概到四五十分了。
之后又用了一个优化是在第一组迭代之后,统计前K条边,统计出每个
V′
点各走了多少次,将走的次数较少的
V′
点反向增加到这个点的概率。这个优化的灵感来自于神经网络里面的反向传播中修改输出层的权重。不得不说其实很多算法的思想都是比较相通的。
整个程序大体框架到这里就差不多结束了,后面一些修修补补也没起到太大的作用。在经过了痛苦的调参之后分数达到78,那个区的排名也最高达16。然而……然而之后分数就一直不涨了,应该是算法的瓶颈吧,排名也一直掉必比赛结束还差点掉出前64。不过也没什么好后悔的,还是实力的问题。
华为还给了个小玩偶做纪念品(衣服太丑被我脱了请不要笑)
下面附上比赛的代码
/*
int WUCHA=0.64;
Update_Probability(p,5);
*/
#include "route.h"
#include "lib_record.h"
#include <stdio.h>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<queue>
#include<cstring>
#include<ctime>
using namespace std;
#define MAX_V_SIZE 605
#define MAX_V1_SIZE 55
#define ITERATOR 100 //迭代次数
double WUCHA;
time_t begin_time;
time_t now_time;
int cost[MAX_V_SIZE+5][MAX_V_SIZE+5];
int Index[MAX_V_SIZE+5][MAX_V_SIZE+5];
int V1[MAX_V1_SIZE];//V0 is number of V' and V1[i]is th real Index of vertex
int is_V1[MAX_V_SIZE];
int probability[MAX_V_SIZE][MAX_V_SIZE];
int link[605][12];//link[i][0]表示i点出度;link[i][1~]保存i所指向的节点
int relink[605][500];//link[i][0]表示i点入度;link[i][1~]保存指向i的节点
bool visit[605];//记录某个节点是否访问过
int SourceID,DestinationID;
struct Path
{
int node_path[605];//从小标1开始
int sumnode;
int sumcost;
int sum_V1;
int score;
}path[8000];
int pathcount=0;
void Init_Read(char *topo[5000],int edge_num, char *demand);//初始化读入
void Init_link();//初始化link数组
void Init_Probability();//初始概率权重
void Init_Path(struct Path &p);
int Pick_Next_Node(int x);//从X点出发根据概率权重选择下一个要走的点
void Find_one_Path(struct Path &p);//找到一条起点到终点的路径存入p中
void Update_Probability(struct Path p,int m);//根据路径p更新概率权重
void Raise_Probability(int a,int b,int m);//提高a到b的概率m%
int Evaluate_Path(struct Path p);//评估一条路径,返回一个0~1000的值
void GA();
void Path_sort();//将path按从优到劣排序
//---------------------------test------------------------------------
void Print_path();
void Print_cost();
void Print_link();
void Print_Probability();
void Init_link()
{
for(int i=0;i<=600;i++)
{
for(int j=0;j<=600;j++)
{
if(cost[i][j]!=0)
{
link[i][++link[i][0]]=j;
}
}
}
}
void Init_relink()
{
for(int i=0;i<=600;i++)
{
for(int j=0;j<=600;j++)
{
if(cost[i][j]!=0)
{
relink[j][++relink[j][0]]=i;
}
}
}
}
//------------------------------------------------蚁群---------------------------------------------------
void Raise_Probability(int a,int b,int m)
/*
a'=a+d*(m/100);
d'=d-d*(m/100);
*/
{
int tempa_probability=0;
int sum_probability=0;
for(int i=1;i<=link[a][0];i++)
sum_probability+=probability[a][link[a][i]];
tempa_probability=probability[a][b];
probability[a][b]+=((sum_probability-probability[a][b])*m)/1000;
for(int i=1;i<=link[a][0];i++)
{
if(link[a][i]!=b)
{
probability[a][link[a][i]]-= (probability[a][link[a][i]] * m) / 1000;
}
}
}
void Init_Probability()
{
int a[10];
for(int i=0;i<=600;i++)
{
if(link[i][0]!=0)
{
for(int j=1;j<=link[i][0];j++)probability[i][link[i][j]]=1000/link[i][0];
}
}
}
int Pick_Previous_Node(int x)
/*
返回-1表示没有点可选
否则返回节点
*/
{
int a[605]={0};
int cnt=0;
int sum_probability=0;
for(int i=1;i<=relink[x][0];i++)
{
if(!visit[relink[x][i]])
a[++cnt]=relink[x][i];
}
if(cnt==0)return -1;
int m=rand()%cnt+1;
return a[m];
}
void Find_one_RePath(int s,struct Path &p)
/*从x到SourceID的反向路径*/
{
Init_Path(p);
int x=s;
visit[x]=1;
p.node_path[++p.sumnode]=s;
while(x!=SourceID)
{
int temp;
temp=Pick_Previous_Node(x);
if(temp==-1)
{
Init_Path(p);
p.node_path[++p.sumnode]=s;
x=s;
continue;
}
else
{
p.node_path[++p.sumnode]=temp;
visit[temp]=1;
x=temp;
continue;
}
}
}
void Back_propagation(int a,int m)
{
struct Path p;
for(int i=1;i<=100;i++)
{
//cout<<i<<endl;
Find_one_RePath(a,p);
struct Path temp;
temp.sumnode=p.sumnode;
for(int j=1;j<=p.sumnode;j++)
{
temp.node_path[j]=p.node_path[p.sumnode-j+1];
}
//for(int i=1;i<=p.sumnode;i++)cout<<temp.node_path[i]<<" ";
//cout<<endl;
temp.sum_V1=V1[0];
Update_Probability(temp,m);
}
}
int Pick_Next_Node(int x)
/*
返回-1表示没有点可选
*/
{
int a[10]={0};
int sum_probability=0;
for(int i=1;i<=link[x][0];i++)
{
if(!visit[link[x][i]])
{
a[i]=a[i-1]+probability[x][link[x][i]];
sum_probability+=probability[x][link[x][i]];
}
else a[i]=a[i-1];
}
if(sum_probability==0)return -1;
int m=rand()%sum_probability+1;
int loc=1;
for(;loc<=link[x][0];loc++)
{
if(a[loc]>=m)
{
return link[x][loc];
}
}
return link[x][link[x][0]] ;
}
void Init_Path(struct Path &p)
{
memset(visit,0,sizeof(visit));
p.sumcost=0;
p.sum_V1=0;
p.sumnode=0;
//p.node_path[++p.sumnode]=SourceID;
return ;
}
void Find_one_Path(struct Path &p)
{
Init_Path(p);
int x=SourceID;
visit[x]=1;
p.node_path[++p.sumnode]=SourceID;
while(x!=DestinationID)
{
int temp;
temp=Pick_Next_Node(x);
if(temp==-1)
{
Init_Path(p);
p.node_path[++p.sumnode]=SourceID;
x=SourceID;
continue;
}
else
{
p.node_path[++p.sumnode]=temp;
visit[temp]=1;
p.sumcost+=cost[x][temp];
if(is_V1[temp])p.sum_V1++;
x=temp;
continue;
}
}
}
void Update_Probability(struct Path p,int m)
/*
如果p.sum_V1与V1[0]误差小于10%,将概率提高10%.
*/
{
double a=V1[0];
double b=p.sum_V1;
double x=(a-b)/V1[0];
if(x<WUCHA)
{
for(int i=1;i<p.sumnode;i++)
{
Raise_Probability(p.node_path[i],p.node_path[i+1],m);//*(0.8/x)
}
}
}
int Cmp(const void *p1,const void *p2)
{
struct Path *c=(struct Path *)p1;
struct Path *d=(struct Path *)p2;
if(c->sum_V1!=d->sum_V1) return d->sum_V1- c->sum_V1;
else return c->sumcost - d->sumcost;
//return (*(struct Path *)p2).sum_V1 > (*(struct Path *)p1).sum_V1 ? 1 : -1;
}
void Path_sort(int sum)//sum为总数
{
qsort(path+1,sum+1,sizeof(path[0]),Cmp);
}
void GA()
{
struct Path p;
while(1)//1000为总群数
{
now_time=time(0);
if(now_time-begin_time>9)break;
Find_one_Path(p);
Update_Probability(p,5);
path[++pathcount]=p;
if(pathcount>=4000)
{
Path_sort(4000);
pathcount=2000;
}
}
Path_sort(6000);
}
void Init_Read(char *topo[5000],int edge_num, char *demand)
/*
初始化输入
cost[i][j]:i到j的边的花费
Index[i][j]:i到j的边的编号
V1中保存V'节点,从V[0]表示个数,从下标1开始
is_V1[]判断某个点(0~600)是否是V'点
*/
{
for(int i=0;i<edge_num;i++)
{
int a[4],t=0,j=0,num=0;
while(topo[i][j]!='\n')
{
if(topo[i][j]==',')
{
a[t++]=num;
num=0;
}
else num=num*10+(int)topo[i][j]-'0';
j++;
}
a[t]=num;
if(cost[a[1]][a[2]]==0 || cost[a[1]][a[2]]>a[3])
{
cost[a[1]][a[2]]=a[3];
Index[a[1]][a[2]]=a[0];
}
}
int a[4000],t=0,i=0,num=0;
while(demand[i]!='\n')
{
if(demand[i]==','||demand[i]=='|')
{
a[t++]=num;
num=0;
}
else num=num*10+(int)demand[i]-'0';
i++;
}
a[t]=num;
SourceID=a[0];DestinationID=a[1];
V1[0]=t-1;
for(int i=1;i<=V1[0];i++)
{
V1[i]=a[i+1];
is_V1[V1[i]]=1;
}
}
void Find_Answer(struct Path p,unsigned short *result)
{
result[0]=p.sumnode-1;
for(int i=1;i<=result[0];i++)
{
result[i]=Index[p.node_path[i]][p.node_path[i+1]];
}
}
void Statistic()
{
int V1cnt[605]={0};
for(int i=1;i<=pathcount;i++)
{
for(int j=1;j<=path[i].sumnode;j++)
{
if(is_V1[path[i].node_path[j]])V1cnt[path[i].node_path[j]]++;
}
}
int sumV1cnt=0;
for(int i=0;i<=605;i++)
{
if(is_V1[i])
{
sumV1cnt+=V1cnt[i];
//cout<<i<<": "<<V1cnt[i]<<endl;
}
}
for(int i=0;i<=605;i++)
{
if(is_V1[i])
{
if(V1cnt[i]<((sumV1cnt/V1[0])))
{
Back_propagation(i,70);
}
}
}
}
//你要完成的功能总入口
void search_route(char *topo[5000], int edge_num, char *demand)
{
srand(time(0));
begin_time=time(0);
Init_Read(topo,edge_num,demand);
//if(V1[0]>20)return ;
if(V1[0]<=10)WUCHA=0;//0~5
else if(V1[0]==12)WUCHA=0.57;//0.55时case6 80分
else if(V1[0]==14)WUCHA=0.42;//7
else WUCHA=0.5;
Init_link();
Init_relink();
unsigned short result[1000];
Init_Probability();
GA();
Statistic();
begin_time=now_time;
GA();
Find_Answer(path[1],result);
if(result[0]==0){result[0]=1;result[1]=1;}
for(int i=1;i<=result[0];i++)
{
record_result(result[i]);
}
}