题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4370
题目大意:有一个n*n的矩阵Cij(1<=i,j<=n),要找到矩阵Xij(i<=1,j<=n)满足以下条件:
1.X 12+X 13+...X 1n=1
2.X 1n+X 2n+...X n-1n=1
3.for each i (1<i<n), satisfies ∑X ki (1<=k<=n)=∑X ij (1<=j<=n).
举个例子, 如果 n=4,上面条件等价于以下情况:
X 12+X 13+X 14=1
X 14+X 24+X 34=1
X 12+X 22+X 32+X 42=X 21+X 22+X 23+X 24
X 13+X 23+X 33+X 43=X 31+X 32+X 33+X 34
找出 ∑C ij*X ij(1<=i,j<=n)的最小值。
解题思路:这题主要是考建图的思维,看了kuangbin写的才会,下面都是照搬他博客的东西。解题的关键在于如何看出这个模型的本质。
3个条件明显在刻画未知数之间的关系,从图论的角度思考问题,容易得到下面3个结论:
①.X12+X13+...X1n=1 于是1号节点的出度为1
②..X1n+X2n+...Xn-1n=1 于是n号节点的入度为1
③.∑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。
故最终答案为min(spfa(1,n),spfa(1,1)+spfa(n,n))。
spfa里面计算最小环,还需要一点修改,当计算环时(dis[start]==INF且start不入队,计算所有start能到达的点并入队)。
代码:
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #include<cstdio> 6 using namespace std; 7 const int N=3e2+5; 8 const int INF=0x3f3f3f3f; 9 10 int n; 11 int cost[N][N],dis[N]; 12 bool vis[N]; 13 14 int spfa(int s,int e){ 15 memset(dis,0x3f,sizeof(dis)); 16 memset(vis,false,sizeof(vis)); 17 queue<int>q; 18 if(s==e){ 19 for(int i=1;i<=n;i++){ 20 if(i!=s){ 21 q.push(i); 22 dis[i]=cost[s][i]; 23 vis[i]=true; 24 } 25 } 26 } 27 else{ 28 dis[s]=0; 29 q.push(s); 30 } 31 while(!q.empty()){ 32 int k=q.front(); 33 q.pop(); 34 vis[k]=false; 35 for(int i=1;i<=n;i++){ 36 if(dis[k]+cost[k][i]<dis[i]){ 37 dis[i]=dis[k]+cost[k][i]; 38 if(!vis[i]){ 39 q.push(i); 40 vis[i]=true; 41 } 42 } 43 } 44 } 45 return dis[e]; 46 } 47 48 int main(){ 49 while(scanf("%d",&n)!=EOF){ 50 for(int i=1;i<=n;i++){ 51 for(int j=1;j<=n;j++){ 52 scanf("%d",&cost[i][j]); 53 } 54 } 55 int ans=min(spfa(1,n),spfa(1,1)+spfa(n,n)); 56 printf("%d\n",ans); 57 } 58 return 0; 59 }