程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> poj 3270 Cow Sorting(初涉置換群)

poj 3270 Cow Sorting(初涉置換群)

編輯:C++入門知識

 

大致題意:給出n個整數,要將它們轉化成遞增序列,每交換其中兩個數的代價是這兩個數之和。問排序成功後的最小代價。

 

該題考察的是置換群知識。在黑書p247上有詳細的講解。總結下置換群,方便復習。

 

群:給定一個集合G={a,b,c...}和集合G上的二元運算 ·,如果滿足封閉性,結合律,存在單位元和逆元,則成集合G在運算'·'之下是一個群。

 

置換:n個元素1,2,....,n之間的置換可表示為

1 2 3 ... n

a1 a2 a3 ... an

表示1被1到n中的某個數a1代替,2被1到n中的某個數a2代替,直到n被1到n中的某個數an代替,且a1,a2....an互不相同。

 

置換群:是若干置換的集合。運算是置換的連接。

 

a1 a2 a3 .... an

循環:記(a1,a2...an) = a2 a3 a4 .... a1 稱為n階循環。每個置換都可以寫成若干個循環的乘積。

 

例如置換1 2 3 4 5 6 = (1 3 6 )(2 5 )(4)

3 5 6 4 2 1

置換的循環節數就是上述循環的個數,例如上面循環節數就是3。

 

回到本題。例如有一個序列: 1 8 9 7 6

要將 1 8 9 7 6 變成有序序列 1 6 7 8 9,可以看做要完成一個置換

1 8 9 7 6

1 6 7 8 9

因為每個置換都可看做若干輪換(循環)的乘積,那麼上述置換可表示成兩個循環(1)(6,7,8,9)的乘積。而我們的目的是變成循環(1)(6)(7)(8)(9),所以每次對換一定是在同一個循環中的兩個元素之間進行。

 

然後對於一個循環i,設它的長度是ki,那麼它至少要交換ki-1次,即每次讓一個元素到達目標位置。既然交換次數一定,那麼要使交換的代價最小,就是每次都拿循環裡最小的元素ti與其他元素交換。根據這些知識,我們得知解決原題,有兩種方案:

1.對於每個循環,都那該循環裡最小的元素ti與其他元素交換,那麼共花費 sumi + (ki-2)*ti,(sumi是該循環個元素之和)

2.讓ti先和n個元素中最小的元素m交換,讓m進入該循環,並和剩下的ki-1個元素依次交換把他們送入目標位置,然後再讓m和ti交換,ti退出該循環。這樣共花費 sumi + ti + (ki+1)*m

 

綜上:所有循環都交換完所需的最小花費cost = sum + ∑min{ (ki-2)*ti , ti + (ki+1)*m };

對於求各個循環,拿兩個數組來回記錄一下就可以了。由上式可以看出我們只需記錄每個循環的ki(長度)和ti(最小元素)。

 

 

#include 
#include 
#include 
#include 
using namespace std;

const int INF = 0x3f3f3f3f;

struct node
{
    int num;
    int Min;
}ans[10010]; //記錄每個循環的長度和最小元素


int main()
{
    int n,a[10010],b[1000010];
    int vis[1000010];
    int cnt,Min;
    int i,t;
    int ans_num;
    int sum;
    int Minn;

    while(~scanf(%d,&n))
    {
        sum = 0;
        Minn = INF;

        for(i = 1; i <= n; i++)
        {
            scanf(%d,&a[i]);
            sum += a[i];
            b[a[i]] = i;
            Minn = min(Minn,a[i]); //n個數中的最小數
        }
        sort(a+1,a+1+n);

        memset(vis,0,sizeof(vis));
        ans_num = 0;
        while(1)
        {
            //每次找出一個未被訪問的數,從這個數開始找其所在的循環
            for(i = 1; i <= n; i++)
                if(!vis[a[i]])
                    break;
            if(i > n)
                break;
                
            cnt = 0; //該循環的元素個數
            Min = INF;//該循環中最小的元素
            t = a[i];
            
            while(1)
            {
                vis[t] = 1;
                Min = min(Min,t);
                cnt++;
                if(!vis[a[b[t]]])
                    t = a[b[t]];
                else break;
            }
            ans[++ans_num] = (struct node){cnt,Min};
        }
        
        for(int i =1; i <= ans_num; i++)
        {
           int num = ans[i].num;
           int Min = ans[i].Min;
           sum += min((num-2)*Min, Min + (num+1)*Minn);
        }
        printf(%d
,sum);

    }
    return 0;
}


 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved