全排列

今天遇到了一道图论题,需要找一条路,需途经所有指定的地点,再到目的地。数据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}的排列枚举树

  1. 系统提示:此处自行脑补
  2. 图二:

这里写图片描述

算法思路

  1. n个元素的全排列=(n-1个元素的全排列)+(另一个元素作为前缀);
  2. 出口:如果只有一个元素的全排列,则说明已经排完,则输出数组;
  3. 不断将每个元素放作第一个元素,然后将这个元素作为前缀,并将其余元素继续全排列,等到出口,出口出去后还需要还原数组;

下面是这个函数的神仙代码,觉得自己很厉害或者想要变得很厉害的可以看一下

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);        //换过去后再还原
            }
        }
    }
}

。。。还真的以为自己很厉害呢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值