今天遇到了一道图论题,需要找一条路,需途经所有指定的地点,再到目的地。数据200以内,并且需要求很多次两个地点的最短路,所以直接无脑套floyd就可以了,之后对于所有的可能路线遍历即可完成建图。但是数据大了这种方法还可以吗?
我不知道。
我在开始做的时候有一个疑虑,怎么样排除两个点之间的最短路不能走的情况,即局部最优不是全局最优的情况,例如 东土大唐到西天要走3秒,西天到浙江要走3秒,东土大唐到浙江要走4秒,东土大唐到浙江的这条路就不能走了。
后面知道了怎么做后,发现“心思缜密”的我再一次想到了。
这题因为最多也就只有9个中转站,所以,只需要对所有的情况进行考虑即可。例如 需要路过2 ,4 and 3,到达目的地5,我们就算一遍:
1 2 3 4 5
1 2 4 3 5
1 3 2 4 5 。。。。
经过我一张草稿纸的计算,最坏的情况也只需要算**次即可。所以决定不会超时的。
这种东西可能就是所谓的全排序吧~
先再次附上我愚陋的代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<list>
#include<vector>
#include<stack>
#include<queue>
#include<functional>
#define D long long
#define F double
#define MAX 0x7fffffff
#define MIN -0x7fffffff
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define pill pair<int, int>
using namespace std;
#define N 10010
#define MOD ((int)1e9+7)
int main()
{
int t,n,m,f;
scanf("%d",&t);
int tt=t;
while(t--)
{
int po[200],val[200][200];
mmm(po,0);mmm(val,10);
scanf("%d%d%d",&n,&m,&f);
for(int i=1;i<n;i++)val[i][i]=0;
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(val[x][y]>z)
val[x][y]=val[y][x]=z;
}
for(int i=1;i<=f;i++)
scanf("%d",&po[i]);
sort(po+1,po+1+f);
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
val[i][j]=min(val[i][j],val[i][k]+val[k][j]);
}
}
}
int mi=MAX;
do{
int ans=0;
ans+=val[1][po[1]];
for(int i=2;i<=f;i++)
ans+=val[po[i-1]][po[i]];
ans+=val[po[f]][n];
mi=min(mi,ans);
}while(next_permutation(po+1,po+1+f));
printf("Case %d: %d\n",tt-t,mi);
}
return 0;
}
全排序以及对应函数
从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。如果这组数有n个,那么全排列数为n!个。
比如对于上面的案例,a,b,c的全排列一共有3!= 6 种 分别是{a, b, c}、{a, c, b}、{b, a, c}、{b, c, a}、{c, a, b}、{c, b, a}。
而我们万能的(除了不能自动生成代码)C++STL,有一个叫做next_permutation,prev_permutation的函数,函数模板:(arr, arr+size)
作用是把指定区域的数组元素改成原先这个区域的“下一个排列组合”和“上一个排列组合”
for example:
int a[3]={1,2,3};
next_permutation(a,a+3);// change to 1,3,2
next_permutation(a,a+2);// change to 3,1,2
prev_permutation(a,a+3);// change to 2,3,1
prev_permutation(a+1,a+3);// change to 2,1,3
cout<<prev_permutation(a+1,a+3)<<endl;//返回类型为bool型,如果可以进行修改
//即存在上一个排列组合,return 1;else return 0;
然后就是原理了
我们可以将这个排列问题画成图形表示,即排列枚举树,比如下图为{1,2,3}的排列枚举树
- 系统提示:此处自行脑补
- 图二:
算法思路:
- n个元素的全排列=(n-1个元素的全排列)+(另一个元素作为前缀);
- 出口:如果只有一个元素的全排列,则说明已经排完,则输出数组;
- 不断将每个元素放作第一个元素,然后将这个元素作为前缀,并将其余元素继续全排列,等到出口,出口出去后还需要还原数组;
下面是这个函数的神仙代码,觉得自己很厉害或者想要变得很厉害的可以看一下
public class hello {
public static int arr[] = new int[]{1,2,3};
public static void main(String[] args) {
perm(arr,0,arr.length-1);
}
private static void swap(int i1, int i2) {
int temp = arr[i2];
arr[i2] = arr[i1];
arr[i1] = temp;
}
/**
* 对arr数组中的begin~end进行全排列
*
* 比如:
* arr = {1,2,3}
* 第一步:执行 perm({1,2,3},0,2),begin=0,end=2;
* j=0,因此执行perm({1,2,3},1,2),begin=1,end=2;
* j=1,swap(arr,0,0)-->arr={1,2,3}, perm({1,2,3},2,2),begin=2,end=2;
* 因为begin==end,因此输出数组{1,2,3}
* swap(arr,1,1) --> arr={1,2,3};
* j=2,swap(arr,1,2)-->arr={1,3,2}, perm({1,3,2},2,2),begin=2,end=2;
* 因为begin==end,因此输出数组{1,3,2}
* swap(arr,2,1) --> arr={1,2,3};
* j=1,swap(arr,0,1) --> arr={2,1,3}, perm({2,1,3},1,2),begin=1,end=2;
* j=1,swap(arr,1,1)-->arr={2,1,3} perm({2,1,3},2,2),begin=2,end=2;
* 因为begin==end,因此输出数组{2,1,3}
* swap(arr,1,1)--> arr={2,1,3};
* j=2,swap(arr,1,2)后 arr={2,3,1},并执行perm({2,3,1},2,2),begin=2,end=2;
* 因为begin==end,因此输出数组{2,3,1}
* swap(arr,2,1) --> arr={2,1,3};
* swap(arr,1,0) --> arr={1,2,3}
* j=2,swap(arr,2,0) --> arr={3,2,1},执行perm({3,2,1},1,2),begin=1,end=2;
* j=1,swap(arr,1,1) --> arr={3,2,1} , perm({3,2,1},2,2),begin=2,end=2;
* 因为begin==end,因此输出数组{3,2,1}
* swap(arr,1,1) --> arr={3,2,1};
* j=2,swap(arr,2,1) --> arr={3,1,2},并执行perm({2,3,1},2,2),begin=2,end=2;
* 因为begin==end,因此输出数组{3,1,2}
* swap(arr,2,1) --> arr={3,2,1};
* swap(arr,0,2) --> arr={1,2,3}
*
*/
public static void perm(int arr[], int begin,int end) {
if(end==begin){ //一到递归的出口就输出数组,此数组为全排列
for(int i=0;i<=end;i++){
System.out.print(arr[i]+" ");
}
System.out.println();
return;
}
else{
for(int j=begin;j<=end;j++){
swap(begin,j); //for循环将begin~end中的每个数放到begin位置中去
perm(arr,begin+1,end); //假设begin位置确定,那么对begin+1~end中的数继续递归
swap(begin,j); //换过去后再还原
}
}
}
}
。。。还真的以为自己很厉害呢~