<题目链接>
题目大意:
一个n*n的01矩阵,满足以下条件
1.X12+X13+...X1n=1
2.X1n+X2n+...Xn-1n=1
3.for each i (1<i<n), satisfies ∑Xki (1<=k<=n)=∑Xij (1<=j<=n).
另给出一个矩阵C,求∑Cij*Xij(1<=i,j<=n)的最小值。
解题分析:
显然,题目给的是一个0/1规划模型。
解题的关键在于如何看出这个模型的本质。
3个条件明显在刻画未知数之间的关系,从图论的角度思考问题,容易得到下面3个结论:
1.X12+X13+...X1n=1 于是1号节点的出度为1
2..X1n+X2n+...Xn-1n=1 于是n号节点的入度为1
3.∑Xki =∑Xij 于是2~n-1号节点的入度必须等于出度
于是3个条件等价于一条从1号节点到n号节点的路径,故Xij=1表示需要经过边(i,j),代价为Cij。Xij=0表示不经过边(i,j)。注意到Cij非负且题目要求总代价最小,因此最优答案的路径一定可以对应一条简单路径。
最终,我们直接读入边权的邻接矩阵,跑一次1到n的最短路即可,记最短路为path。
以上情况设为A
非常非常非常非常非常非常非常非常抱歉,简单路径只是充分条件,但不必要。(对造成困扰的队伍深表歉意)
漏了如下的情况B:
从1出发,走一个环(至少经过1个点,即不能是自环),回到1;从n出发,走一个环(同理),回到n。
容易验证,这是符合题目条件的。且A || B为该题要求的充要条件。
由于边权非负,于是两个环对应着两个简单环。
因此我们可以从1出发,找一个最小花费环,记代价为c1,再从n出发,找一个最小花费环,记代价为c2。(只需在最短路算法更新权值时多加一条记录即可:if(i==S) cir=min(cir,dis[u]+g[u][i]))
故最终答案为min(path,c1+c2)。
Dijkstra代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 const int N =305; 7 #define INF 0x3f3f3f3f 8 int n; 9 int cost[N][N]; 10 int dis[N]; 11 bool vis[N]; 12 13 void dij(int s){ 14 memset(vis,false,sizeof(vis)); 15 for(int i=1;i<=n;i++){ //初始化 16 if(i==s)dis[i]=INF; //将s的dis值初始化为INF,这样就可以从其它点开始松弛最短路,从而得到s的闭环长度,因为其它点cur的dis初始化为cost[s][cur]。所以用s到cur的距离,再加上cur到s的最短距离,确实是s的最小非自环 17 else if(cost[s][i]!=INF)dis[i]=cost[s][i]; 18 else dis[i]=INF; 19 } 20 for(int k=1;k<=n;k++){ 21 int mn=INF,cur; 22 for(int i=1;i<=n;i++){ 23 if(!vis[i]&&dis[i]<mn){ 24 mn=dis[i]; 25 cur=i; 26 } 27 } 28 vis[cur]=true; 29 for(int i=1;i<=n;i++){ 30 if(!vis[i]&&dis[i]>dis[cur]+cost[cur][i]){ 31 dis[i]=dis[cur]+cost[cur][i]; 32 } 33 } 34 } 35 } 36 37 int main(){ 38 while(scanf("%d",&n)!=EOF){ 39 for(int i=1;i<=n;i++){ 40 for(int j=1;j<=n;j++){ 41 scanf("%d",&cost[i][j]); 42 } 43 } 44 dij(1); 45 int loop1,loopn,dist; //loop1为1的最短非自环,loop2为n的最短非自环 46 loop1=dis[1],dist=dis[n]; 47 dij(n); 48 loopn=dis[n]; 49 int ans=min(dist,loop1+loopn); 50 printf("%d ",ans); 51 } 52 return 0; 53 }
spfa代码:
1 #include<stdio.h> 2 #include<iostream> 3 #include<string.h> 4 #include<algorithm> 5 using namespace std; 6 7 const int INF=0x3f3f3f3f; 8 const int MAXN=330; 9 int cost[MAXN][MAXN];//保存路径长度的邻接矩阵 10 int dist[MAXN]; 11 int que[MAXN];//注意队列的循环利用,建成循环队列 12 bool vis[MAXN];//是否在队列中标记 13 14 void SPFA(int start,int n) 15 { 16 int front=0,rear=0; 17 for(int v=1;v<=n;v++)//初始化 18 { 19 if(v==start)//由于要找start的闭环,所以dist[start]设为INF,且不入队 20 { 21 dist[v]=INF; 22 vis[v]=false; 23 } 24 else if(cost[start][v]!=INF) 25 { 26 dist[v]=cost[start][v]; 27 que[rear++]=v; 28 vis[v]=true; 29 } 30 else//即dist[start][v]==INF情况,对本题没有这种情况 31 { 32 dist[v]=INF; 33 vis[v]=false; 34 } 35 } 36 37 while(front!=rear)//注意这个条件是不等,因为是循环队列 38 { 39 int u=que[front++]; 40 for(int v=1;v<=n;v++) 41 { 42 if(dist[v]>dist[u]+cost[u][v]) 43 { 44 dist[v]=dist[u]+cost[u][v]; 45 if(!vis[v])//不在队列 46 { 47 vis[v]=true; 48 que[rear++]=v; 49 if(rear>=MAXN) rear=0;//循环队列 50 } 51 } 52 } 53 vis[u]=false; 54 if(front>=MAXN)front=0; 55 } 56 57 } 58 int main(){ 59 int n; 60 while(scanf("%d",&n)!=EOF){ 61 for(int i=1;i<=n;i++) 62 for(int j=1;j<=n;j++) 63 scanf("%d",&cost[i][j]); 64 SPFA(1,n); 65 int ans=dist[n];//1到n的最短路 66 int loop1=dist[1];//1的闭环长度 67 SPFA(n,n); 68 int loopn=dist[n];//n的闭环长度 69 ans=min(ans,loop1+loopn); 70 printf("%d ",ans); 71 } 72 return 0; 73 }
2018-10-14