poj数算A上机汇总4 排序的代价(置换群+贪心)

poj数算A上机汇总4 排序的代价(置换群+贪心)
Time Limit: 10000ms total,5000ms each Memory Limit: 65536kB

Description
现有一排装满货物的箱子,重量各不相同(都已标示在箱子上),为了进行后面的工作,需要将这些箱子按轻重有序放置,但只有一名工作人员来完成这项工作,由于空间有限,他只能通过不断交换两个箱子(可不相邻)的位置的方式来实现箱子的排序。他知道这样一定可以完成任务,但搬箱子很累,所以他希望找到一种最省力的方式来完成工作,假设一次交换两个箱子的代价为这两个箱子的重量之和,那么这项工作的总代价为此过程中所有“交换”的代价之和,请帮助他计算排列这些箱子的最小代价。

Input
输入包含多个数据实例,每个实例独占一行,每行数据是以空格分隔的非负整数,其中第一个整数表示箱子的个数(n),接下来为n个不同的正整数,分别表示n个箱子的重量,其顺序表示了箱子的初始顺序;当n=0时表示输入结束。

Output
对每个有效数据实例(n!=0)都输出一个整数,独占一行,表示该实例中排序箱子的最小代价。

Sample Input

3 3 2 1
4 8 1 2 4
5 1 8 9 7 6
6 8 4 5 3 2 7
0

Sample Output

4
17
41
34

Hint
示例解释:
共有4个有效数据实例:
第一个数据实例为3 2 1,通过交换重为3和1的箱子即可,总代价为3+1=4;
第二个数据实例为8 1 2 4,代价最小的交换过程为
1↔2,1↔4,1↔8,总代价为(1+2)+(1+4)+(1+8) = 17;
第三个数据实例为1 8 9 7 6,代价最小的交换过程为
1↔6,1↔9,1↔7,1↔8,1↔6,总代价为(1+6)+(1+9)+(1+7)+(1+8)+(1+6)=41;
第四个数据实例为8 4 5 3 2 7,代价最小的交换过程为
3↔5,3↔4,2↔7,2↔8,总代价为(3+5)+(3+4)+(2+7)+(2+8)=34。

请注意边界条件和IO,n可能很大。


做完这道题目看网上题解时候发现有一篇题解指出:涉及可以不相邻的交换代价的题目一般都与置换群有关。也许这是久经沙场的ACM/OI老将的经验直觉,这个题目虽然可以看作置换群,但是一开始想到置换群的圈结构是不容易的,而且过分依赖于经验直觉直接认为它是置换群的题目再去凑解法也是不自然的,所以我们要从头开始分析这个题目。

首先,考虑到两个因素可以是代价小:1.尽量移动小元素做中转。2.如果把总置换看作一次一次置换的乘法,尽量不要牵涉多余的元素进入一次置换。
比如(300 400 500 200)->(200 300 400 500)这个一次置换,在这两个因素指导下,尽量在内部换,而且尽量多与200交换。那么,可以得到一个代价200*3+300+400+500,似乎问题得到了解决,可以发现如果严格遵守2.,一个长为n的圈A的最代价为minA * (n-2)+sum A,由置换群理论,所有的置换都可以分解为圈。
但是这样一来如果允许外面元素交换入内,比如外面存在一个全置换群最小元素,比如1,与200换后,事实上可以减小代价。这也是应当考虑的,那么,如果可以交换进来,是不是只能交换一个全置换群最小元素呢?是正确的,因为200的角色只需要一个元素扮演,那么经过代数推算发现是否应该交换与200和1的相对大小有关,所以如果全置换群最小元素为m,一个圈A的最小代价为min cost A=sumA+min{minA*(n-2),minA+(n+1) * m}。
这就是置换群+贪心算法的由来,虽然细节没有证明,但是证明基本严谨。

下面具体实现起来还有一个问题,就是如何在置换群里找出圈。本题使用的是并查集方法,具体见find_root()。


Accepted    19464kB 848ms   1340 B  G++ 
#define MAX_N 1000000

#include<stdio.h>
#include<stdlib.h>

struct data_type
{
    int code;
    int data;
};

int n,ans,r;
data_type a[MAX_N];
int root[MAX_N],sum[MAX_N],min[MAX_N],num[MAX_N];//置换群里面的环的信息 

int compare(const void* p1,const void* p2)
{
    return ((const data_type*) p1)->data-((const data_type*) p2)->data; 
}

int find(int x)
{
    if (root[x]==x)
        return x;
    else
        return root[x]=find(root[x]); 
}

inline int Min(int x,int y)
{
    return x<y?x:y;
}

void find_loop()
{
    #ifdef TEST 
    for (int i=0;i<n;i++)
        printf("%d(%d) ",a[i].data,a[i].code);
    printf("\n");
    #endif
    for (int i=0;i<n;i++)
        root[i]=i;      
    for (int i=0;i<n;i++)
        root[find(i)]=find(a[i].code);
    #ifdef TEST
    for (int i=0;i<n;i++)
        printf("%d ",root[i]);
    printf("\n");
    #endif
    return;
}

int main()
{
    while (scanf("%d",&n)&&n)
    {
        ans=0;
        for (int i=0;i<n;i++)
            scanf("%d",&a[i].data);
        for (int i=0;i<n;i++)
            a[i].code=i;
        qsort(a,n,sizeof(data_type),compare);
        find_loop();
        for (int i=0;i<n;i++)
            sum[i]=min[i]=num[i]=0;
        for (int i=0;i<n;i++)
        {   
            r=find(i);
            if (!num[r])
                min[r]=a[i].data;
            sum[r]+=a[i].data;
            num[r]++;
        }
        for (int i=0;i<n;i++)
        { 
            r=find(i);
            if (num[r]) 
                ans+=sum[r]+Min((num[r]-2)*min[r],min[r]+(num[r]+1)*a[0].data);
            num[r]=0;
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值