寒假训练第五天

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. 1到n有两条独立的最短路,最小代价为两条最短路之和。
  2. 1到n有且仅有一条最短路并且有与最短路独立的次短路,最小代价为最短路权和+次短路的权和。
  3. 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值