E- HDU-4370 0 or 1
题意:
给一个N * N的矩阵,让你自己找一个矩阵(矩阵的数非1即0),并求出∑C ij*X ij(1<=i,j<=n)的最小值。
思路:
因为Xil非0即1.所以∑C ij*X ij的最小值可以转化为选中一些Cij求和。
我们用图的思路分析题目所给条件:
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出发,走一个环(不能是自环),回到1;从n出发,走一个环(同理),回到n。其他点度数为0,由于边权非负,于是两个环对应着两个简单环。
因此我们可以从1出发,找一个最小花费环,记代价为c1,再从n出发,找一个最小花费环,记代价为c2。(只需在最短路算法更新权值时多加一条记录即可:if(i==S) cir=min(cir,dis[u]+g[u][i]))
代码:
#include <iostream>
#include <cstring>
#include <string>
#include <map>
#include <stack>
#include <queue>
#include <set>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <list>
#include<cstdlib>
using namespace std;
#define pi acos(-1.0)
typedef long long ll;
const int inf = 1000000007;
const int maxn = 300;
const double eps = 1e-12;
typedef double db;
const ll mod = 998244353;
int n = 0, m, k;
int mp[maxn][maxn], dis[maxn];
int vis[maxn];
void spfa(int ss)
{
queue<int >s;
for (int i = 0; i < n; i++){
if (i == ss){
dis[i] = inf;
vis[i] = 0;
}
else{
dis[i] = mp[ss][i];
vis[i] = 1;
s.push(i);
}
}
while (!s.empty()){
int u = s.front();
s.pop();
vis[u] = 0;
for (int v = 0; v < n; v++){
if (dis[v] > dis[u] + mp[u][v]){
dis[v] = dis[u] + mp[u][v];
if (vis[v] == 0){
vis[v] = 1;
s.push(v);
}
}
}
}
}
int main(){
while (~scanf("%d", &n)){
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%d", &mp[i][j]);
spfa(0);
int dis1 = dis[0];
int dis2 = dis[n - 1];
spfa(n - 1);
int dis3 = dis[n - 1];
int output = min(dis2, dis1 + dis3);
printf("%d\n", output);
}
}
F - uva 10806 Dijkstra, Dijkstra.
题意:
固定起点1和终点n,从1到n,再从n回到1,去和回的路上相同的边只能用一次,求两次的和最短,如果去的时候不能去到终点或者回的时候回不到起点那么就输出Back to jail,否则输出两次和的最小值(此图是无向图,不会有重边,边的权值在大于1小于1000)
思路:
这道题看似是求两次最短路就可以了实则不是。
我们将从1到n的走法分为三种情况:
- 1到n有两条独立的最短路,最小代价为两条最短路之和。
- 1到n有且仅有一条最短路并且有与最短路独立的次短路,最小代价为最短路权和+次短路的权和。
- 1到n最短路不唯一且两条或者多条最短路有共用边 ,或者最短路与次短路有共用边。(想象一下七段数码管的“8”,假设S在左上角,T在右下角,中间那一“横”的权是1,其他全是2。如果走最短路,即经过中间,回来就没路了。正确的走法是沿着旁边走)
如果仅仅是将第一次求最短路时将边标记再求最短路,那么在第三种情况下,第二条最短路(次短路)就走不通,导致WA。
因为有共用边,所以我们将两条最短路分为三部分,前独立部分+共用边+后独立部分。我们将无向图转化为有向图,将走过的边去向标记,反方向转为负权。因为有负边第二次最短路跑一遍spfa,两次最短路相加就是答案。将边的反方向标为负权,如果第二次还有用到这条边,就相当于与第一次的边权相抵=没走这条边,也就是相当于将两条最短路的两个前部分(后部分)交换使路径改变,但总距离不变。
代码↓
#include <iostream>
#include <cstring>
#include <string>
#include <map>
#include <stack>
#include <queue>
#include <set>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <list>
#include<cstdlib>
using namespace std;
#define pi acos(-1.0)
typedef long long ll;
const int inf = 1000000007;
const int maxn = 300;
const double eps = 1e-12;
typedef double db;
const ll mod = 998244353;
int n = 0, m, k;
int w[maxn][maxn], dis[maxn];
int vis[maxn], fa[maxn];
void spfa()
{
memset(vis, 0, sizeof(vis));
memset(fa, -1, sizeof(fa));
dis[1] = 0;
for (int i = 2; i <= n; i++)
dis[i] = inf;
queue<int> q;
q.push(1);
vis[1] = 1;
while (!q.empty()){
int x = q.front();
q.pop();
vis[x] = 0;
for (int y = 1; y <= n; y++){
if (dis[y] > dis[x] + w[x][y]) {
dis[y] = dis[x] + w[x][y];
fa[y] = x;
if (!vis[y]){
vis[y] = 1;
q.push(y);
}
}
}
}
}
int main()
{
while (~scanf("%d", &n), n)
{
scanf("%d", &m);
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++)
w[i][j] = w[j][i] = (i == j ? 0 : inf);
for (int i = 0; i < m; i++)
{
int u, v, x;
scanf("%d%d%d", &u, &v, &x);
w[u][v] = w[v][u] = x;
}
spfa();
int ans = dis[n];
int s = 1, t = n;
while (t != s){
w[t][fa[t]] = -w[t][fa[t]];
w[fa[t]][t] = inf;
t = fa[t];
}
spfa();
if (ans == inf || dis[n] == inf){
printf("Back to jail\n");
continue;
}
printf("%d\n", ans + dis[n]);
}
return 0;
}