2014年3月1日 星期六

Merge sort

演算法類型

divide and conquer, 排序演算法

演算法目的

利用比較 key value 來將資料做排序

演算法描述

merge sort 的核心觀念是將大筆資料切割成很多小筆資料做排序,接著利用已經排序好的小筆資料合併成排序好的大筆資料。merge sort 的概觀流程圖如下:


分割步驟相信大家應該沒有什麼問題,就是一次將資料切一半。比較需要解釋的應該是合併步驟,以下舉例說明合併步驟如何進行,假設要合併 A 和 B 兩個已排序好的數列,如下圖:


現在我們將兩個箭頭各指著 A 數列和 B 數列的第一個元素,如下圖:


將箭頭指到的數字做比較,把比較小的數字複製到另外一個 C 數列,並且將指向比較小的數字的箭頭往前移一格,如下圖:


繼續重複剛剛的動作,如下圖:


繼續重複這個動作直到有其中一個箭頭超出數列的範圍為止,如下圖:





現在 B 數列的箭頭已經超出範圍了,所以我們剩下要做的事情就只是把 A 數列箭頭開始以後的數字全部複製到 C 數列就可以了,如下圖:



這樣 C 數列就是一個合併完成的數列了。

最壞情況時間複雜度分析

設總資料量為 n, 分析單位為比較次數
依據此演算法的最壞情況,我們可以列出以下遞迴式:

T(n) = 2T(n / 2) + n - 1
T(1) = 0

根據 master theorem,可以求出此演算法為 O(nlgn) 的演算法

程式實作


C
#include <stdio.h>

void mergeSort(int *, int, int);
void merge(int *, int, int, int);

int main() {
    int arr[8] = {4, 6, 1, 9, 5, 3, 0, 2};
    int dataNum = 8;
    int i;

    printf("before sorting: ");
    for (i=0; i<dataNum; i++)
        printf("%d " , arr[i]);
    printf("\n");
    mergeSort(arr, 0, dataNum - 1);
    printf("after sorting: ");
    for (i=0; i<dataNum; i++)
        printf("%d " , arr[i]);
    printf("\n");

    return 0;
}

void mergeSort(int *arr, int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;

        mergeSort(arr, low, mid);
        mergeSort(arr, mid + 1, high);
        merge(arr, low, mid, high);
    }
}

void merge(int *arr, int low, int mid, int high) {
    int leftIndex = low;
    int rightIndex = mid + 1;
    int tempArrLength = high - low + 1;
    int tempArr[tempArrLength];
    int tempIndex = 0;

    while (leftIndex <= mid && rightIndex <= high) {
        if (arr[leftIndex] <= arr[rightIndex]) {
            tempArr[tempIndex] = arr[leftIndex];
            leftIndex++;
        }
        else {
            tempArr[tempIndex] = arr[rightIndex];
            rightIndex++;
        }
        tempIndex++;
    }
    if (leftIndex > mid) {
        while (rightIndex <= high) {
            tempArr[tempIndex] = arr[rightIndex];
            rightIndex++;
            tempIndex++;
        }
    }
    else {
        while (leftIndex <= mid) {
            tempArr[tempIndex] = arr[leftIndex];
            leftIndex++;
            tempIndex++;
        }
    }
    leftIndex = low;
    for (tempIndex=0; tempIndex<tempArrLength; tempIndex++) {
        arr[leftIndex] = tempArr[tempIndex];
        leftIndex++;
    }
}

Java
public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {4, 6, 1, 9, 5, 3, 0, 2};

        System.out.print("before sorting: ");
        for (int num: arr)
            System.out.printf("%d " , num);
        System.out.println();
        mergeSort(arr, 0, arr.length - 1);
        System.out.print("after sorting: ");
        for (int num: arr)
            System.out.printf("%d " , num);
        System.out.println();
    }

    private static void mergeSort(int[] arr, int low, int high) {
        if (low < high) {
            int mid = (low + high) / 2;

            mergeSort(arr, low, mid);
            mergeSort(arr, mid + 1, high);
            merge(arr, low, mid, high);
        }
    }

    private static void merge(int[] arr, int low, int mid, int high) {
        int leftIndex = low;
        int rightIndex = mid + 1;
        int[] tempArr = new int[high - low + 1];
        int tempIndex = 0;

        while (leftIndex <= mid && rightIndex <= high) {
            if (arr[leftIndex] <= arr[rightIndex]) {
                tempArr[tempIndex] = arr[leftIndex];
                leftIndex++;
            }
            else {
                tempArr[tempIndex] = arr[rightIndex];
                rightIndex++;
            }
            tempIndex++;
        }
        if (leftIndex > mid) {
            while (rightIndex <= high) {
                tempArr[tempIndex] = arr[rightIndex];
                rightIndex++;
                tempIndex++;
            }
        }
        else {
            while (leftIndex <= mid) {
                tempArr[tempIndex] = arr[leftIndex];
                leftIndex++;
                tempIndex++;
            }
        }
        leftIndex = low;
        for (int temp: tempArr) {
            arr[leftIndex] = temp;
            leftIndex++;
        }
    }
}

Python
def merge(arr, low, mid, high):
    leftIndex = low
    rightIndex = mid + 1
    tempArr = []
    while leftIndex <= mid and rightIndex <= high:
        if arr[leftIndex] <= arr[rightIndex]:
            tempArr.append(arr[leftIndex])
            leftIndex += 1
        else:
            tempArr.append(arr[rightIndex])
            rightIndex += 1
    if leftIndex > mid:
        while rightIndex <= high:
            tempArr.append(arr[rightIndex])
            rightIndex += 1
    else:
        while leftIndex <= mid:
            tempArr.append(arr[leftIndex])
            leftIndex += 1
    leftIndex = low
    for temp in tempArr:
        arr[leftIndex] = temp
        leftIndex += 1

def mergeSort(arr, low, high):
    if low < high:
        mid = (low + high) / 2
        mergeSort(arr, low, mid)
        mergeSort(arr, mid + 1, high)
        merge(arr, low, mid, high)

arr = [4, 6, 1, 9, 5, 3, 0, 2]
print "before sorting:",
for num in arr:
    print "%d" % (num),
print ""
mergeSort(arr, 0, len(arr) - 1)
print "after sorting:",
for num in arr:
    print "%d" % (num),
print ""

2014年2月27日 星期四

Selection sort

演算法類型

貪婪演算法, 排序演算法

演算法目的

藉由比較key value來將資料做排序

演算法描述

selection sort的精神是將資料切成兩部份,一部份爲已排序另一部份則是尚未排序,並且每次於尚未排序的資料堆中找出key value最小的資料放入已排序資料堆中的最後一個位置。舉例來說,有個數列如下:


現在此數列所有的數字都在未排序的資料堆裡,我們可以由左至右對數列做一次掃描,找出最小的數字1,並且將它與數列的第一個數字3做交換,這樣我們就可以把1歸納在已排序的資料堆裡,如下圖:



接著繼續從剩下未排序的資料堆裡,找出最小的數字3,並且將它與未排序資料堆的第一個數字5做交換,這樣就可以讓3被歸納在已排序的資料堆裡,如下圖:





再繼續從剩下未排序的資料堆裡,找出最小的數字4,並且將它與未排序資料堆的第一個數字9做交換,這樣就可以讓4被歸納在已排序的資料堆裡,如下圖:




依此類推,不斷地重複這些步驟就可以把全部的資料都排序好了。

效率分析

設總資料量爲n, 分析單位爲比較次數

第一次掃描找到最小值的比較次數: n - 1次
第二次掃描找到最小值的比較次數: n - 2次
第三次掃描找到最小值的比較次數: n - 3次

 ...

第n次掃描找到最小值的比較次數: 0次

總共比較次數: (0 + n - 1) * n / 2 = (n ^ 2 - n) / 2
故此演算法屬於O(n ^ 2)

程式實作


C
#include <stdio.h>

void selSort(int *, int);

int main() {
    int arr[8] = {3, 5, 9, 10, 8, 1, 12, 4};
    int dataNum = 8;
    int i;

    printf("before sorting: ");
    for (i=0; i<dataNum; i++)
        printf("%d ", arr[i]);
    printf("\n");
    selSort(arr, dataNum);
    printf("after sorting: ");
    for (i=0; i<dataNum; i++)
        printf("%d ", arr[i]);
    printf("\n");

    return 0;
}

void selSort(int *arr, int dataNum) {
    int i, j;

    for (i=0; i<dataNum; i++) {
        int smallestIndex = i;

        for (j=i+1; j<dataNum; j++) {
            if (arr[smallestIndex] > arr[j])
                smallestIndex = j;
        }
        if (smallestIndex != i) {
            int temp = arr[smallestIndex];

            arr[smallestIndex] = arr[i];
            arr[i] = temp;
        }
    }
}

Java
public class SelSort {
    public static void main(String[] args) {
        int[] arr = {3, 5, 9, 10, 8, 1, 12, 4};

        System.out.print("before sorting: ");
        for (int num: arr)
            System.out.printf("%d ", num);
        System.out.println();
        selSort(arr);
        System.out.print("after sorting: ");
        for (int num: arr)
            System.out.printf("%d ", num);
        System.out.println();
    }

    private static void selSort(int[] arr) {
        for (int i=0; i<arr.length; i++) {
            int smallestIndex = i;

            for (int j=i+1; j<arr.length; j++) {
                if (arr[smallestIndex] > arr[j])
                    smallestIndex = j;
            }
            if (smallestIndex != i) {
                int temp = arr[smallestIndex];

                arr[smallestIndex] = arr[i];
                arr[i] = temp;
            }
        }
    }
}

Python
def selSort(arr):
    for i in range(len(arr)):
        smallestIndex = i

        for j in range(i + 1, len(arr)):
            if arr[smallestIndex] > arr[j]:
                smallestIndex = j
        if smallestIndex != i:
            temp = arr[smallestIndex]
            arr[smallestIndex] = arr[i]
            arr[i] = temp

arr = [3, 5, 9, 10, 8, 1, 12, 4]
print "before sorting:",
for num in arr:
    print num,
print ""
selSort(arr)
print "after sorting:",
for num in arr:
    print num,
print ""

2014年2月8日 星期六

101 - The Blocks Problem


Background 

Many areas of Computer Science use simple, abstract domains for both analytical and empirical studies. For example, an early AI study of planning and robotics (STRIPS) used a block world in which a robot arm performed tasks involving the manipulation of blocks.
In this problem you will model a simple block world under certain rules and constraints. Rather than determine how to achieve a specified state, you will ``program'' a robotic arm to respond to a limited set of commands.

The Problem 

The problem is to parse a series of commands that instruct a robot arm in how to manipulate blocks that lie on a flat table. Initially there are nblocks on the table (numbered from 0 to n-1) with block bi adjacent to block bi+1 for all $0 \leq i < n-1$ as shown in the diagram below:

\begin{figure}
\centering
\setlength{\unitlength}{0.0125in} %
\begin{picture}
(2...
...raisebox{0pt}[0pt][0pt]{$\bullet
\bullet \bullet$ }}}
\end{picture}
\end{figure}
Figure: Initial Blocks World

The valid commands for the robot arm that manipulates blocks are:
  • move a onto bwhere a and b are block numbers, puts block a onto block b after returning any blocks that are stacked on top of blocks a and b to their initial positions.
  • move a over bwhere a and b are block numbers, puts block a onto the top of the stack containing block b, after returning any blocks that are stacked on top of block a to their initial positions.
  • pile a onto bwhere a and b are block numbers, moves the pile of blocks consisting of block a, and any blocks that are stacked above block a, onto block b. All blocks on top of block b are moved to their initial positions prior to the pile taking place. The blocks stacked above block aretain their order when moved.
  • pile a over bwhere a and b are block numbers, puts the pile of blocks consisting of block a, and any blocks that are stacked above block a, onto the top of the stack containing block b. The blocks stacked above block a retain their original order when moved.
  • quitterminates manipulations in the block world.
Any command in which a = b or in which a and b are in the same stack of blocks is an illegal command. All illegal commands should be ignored and should have no affect on the configuration of blocks.

The Input 

The input begins with an integer n on a line by itself representing the number of blocks in the block world. You may assume that 0 < n < 25.
The number of blocks is followed by a sequence of block commands, one command per line. Your program should process all commands until the quit command is encountered.
You may assume that all commands will be of the form specified above. There will be no syntactically incorrect commands.

The Output 

The output should consist of the final state of the blocks world. Each original block position numbered i ( $0 \leq i < n$ where n is the number of blocks) should appear followed immediately by a colon. If there is at least a block on it, the colon must be followed by one space, followed by a list of blocks that appear stacked in that position with each block number separated from other block numbers by a space. Don't put any trailing spaces on a line.
There should be one line of output for each block position (i.e., n lines of output where n is the integer on the first line of input).

Sample Input 

10
move 9 onto 1
move 8 over 1
move 7 over 1
move 6 over 1
pile 8 over 6
pile 8 over 5
move 2 over 1
move 4 over 9
quit

Sample Output 

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

出處: http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=3&page=show_problem&problem=37



問題敘述

此問題首先會輸入積木的個數,接著會輸入一連串的指令來告訴機器手臂該如何搬動積木,一直到輸入quit才結束。假設積木的總數量為n,積木會從0開始編號到n-1,指令的形式為: move/pile 積木編號 onto/over 積木編號,舉例: move 9 onto 1 意思就是把9號積木移動到1號積木的上面,若9號積木或1號積木上面有其他積木,就把上面的積木都移回原位。move 8 over 1 意思就是把8號積木移動到有1號積木的積木堆上面,若8號積木上面有其他積木,就把上面的積木都移回原位。pile 8 onto 6 意思就是把8號以上的積木全部移動到6號積木的上面,若6號積木上面有其他積木,就把上面的積木都移回原位。pile 8 over 6 意思就是把8號以上積木全部移動到有6號積木的積木堆上面。

解題思路

沒有什麼特別的思路,照著做就可以求解。不過在寫程式上卻有一些技巧,因為很多指令的動作是有重複的,所以可以把它寫成function呼叫。比如說"將某個積木上面的所有積木搬回原位"這個動作是很常用到的,所以可以寫成function比較方便。還有要注意的一點是如果指令裡的2個積木編號是在同一個積木堆上,則此指令無效,必須忽略此指令否則會wrong answer。

c++ 程式碼

#include <iostream>
#include <vector>
using namespace std;

void moveBackToOri(vector< vector<int> >& blocks, vector<int>& blockPos, int pos, int theBlock) {
    while (blocks[pos].back() != theBlock) {
        int last = blocks[pos].back();

        blocks[pos].pop_back();
        blocks[last].push_back(last);
        blockPos[last] = last;
    }
}

void pileBlocks(vector< vector<int> >& blocks, vector<int>& blockPos, int pos, int theBlock, vector<int>& temp) {
    while (true) {
        int last = blocks[pos].back();

        blocks[pos].pop_back();
        temp.push_back(last);
        if (last == theBlock)
            break;
    }
}

int main() {
    int blockNum;

    cin >> blockNum;
    vector< vector<int> > blocks(blockNum, vector<int>());
    vector<int> blockPos(blockNum, -1);
    for (int i=0; i<blocks.size(); i++) {
        blocks[i].push_back(i);
        blockPos[i] = i;
    }
    while (true) {
        string action, where;
        int fromBlock, toBlock;
        int fromPos;
        int toPos;

        cin >> action;
        if (action == "quit")
            break;
        cin >> fromBlock >> where >> toBlock;
        fromPos = blockPos[fromBlock];
        toPos = blockPos[toBlock];
        if (fromPos != toPos) {
            if (action == "move") {
                if (where == "onto") {
                    moveBackToOri(blocks, blockPos, fromPos, fromBlock);
                    moveBackToOri(blocks, blockPos, toPos, toBlock);
                    blocks[fromPos].pop_back();
                    blocks[toPos].push_back(fromBlock);
                    blockPos[fromBlock] = toPos;
                }
                else {
                    moveBackToOri(blocks, blockPos, fromPos, fromBlock);
                    blocks[fromPos].pop_back();
                    blocks[toPos].push_back(fromBlock);
                    blockPos[fromBlock] = toPos;
                }
            }
            else {
                vector<int> temp;

                if (where == "onto") {
                    pileBlocks(blocks, blockPos, fromPos, fromBlock, temp);
                    moveBackToOri(blocks, blockPos, toPos, toBlock);
                    while (!temp.empty()) {
                        int last = temp.back();

                        temp.pop_back();
                        blocks[toPos].push_back(last);
                        blockPos[last] = toPos;
                    }
                }
                else {
                    pileBlocks(blocks, blockPos, fromPos, fromBlock, temp);
                    while (!temp.empty()) {
                        int last = temp.back();

                        temp.pop_back();
                        blocks[toPos].push_back(last);
                        blockPos[last] = toPos;
                    }
                }
            }
        }
    }
    for (int i=0; i<blocks.size(); i++) {
        cout << i << ":";
        for (int j=0; j<blocks[i].size(); j++)
            cout << " " << blocks[i][j];
        cout << endl;
    }

    return 0;
}

2014年2月1日 星期六

105 - The Skyline Problem



出處: http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=3&page=show_problem&problem=41


問題敘述

此問題會輸入很多組長方形建築物的資料,每組資料分別是: 左x座標, 高度, 右x座標,輸入資料會照著左x座標由小到大排序,得到所有建築物的資料之後,必須輸出"天際線向量"。所謂天際線向量,就是用一個長的像這樣子的向量: (v1, v2, v3, ... , vn) 來表示天際線,奇數位置填的是x座標,偶數位置填的是高度,必須從最小的x座標開始填起。

解題思路

這題有一個可以減少我們很多功夫的關鍵點,那就是所有座標都是小於10,000的整數,也就是說我們只要開一個10,000個元素的陣列,暴力記下每一個x座標的最高點,就可以完成天際線向量了。所以此題的演算法大致上是如此: 每讀入一組建築物資料,就更新那一區塊的所有x座標的最高點(注意不要更新到建築物最右邊座標的高度,因為這樣才能知道哪個x座標開始產生高度變化),最後只要從小到大把每個x座標的最高點掃描一遍,就可以印出天際線向量了。

c++ 程式碼

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int left, right, height, last;
    int first = -1;
    int nowHeight = 0;
    vector<int> heightVec(10000, 0);

    while (cin >> left >> height >> right) {
        if (first == -1) {
            first = left;
            last = right;
        }
        if (right > last)
            last = right;
        for (int i=left; i<right; i++) {
            if (height > heightVec[i])
                heightVec[i] = height;
        }
    }
    for (int i=first; i<=last; i++) {
        if (heightVec[i] != nowHeight) {
            cout << i << " " << heightVec[i];
            if (i == last)
                cout << endl;
            else {
                cout << " ";
                nowHeight = heightVec[i];
            }
        }
    }

    return 0;
}

2013年9月2日 星期一

104 - Arbitrage

Background

The use of computers in the finance industry has been marked with controversy lately as programmed trading -- designed to take advantage of extremely small fluctuations in prices -- has been outlawed at many Wall Street firms. The ethics of computer programming is a fledgling field with many thorny issues.

The Problem

Arbitrage is the trading of one currency for another with the hopes of taking advantage of small differences in conversion rates among several currencies in order to achieve a profit. For example, if $1.00 in U.S. currency buys 0.7 British pounds currency, £1 in British currency buys 9.5 French francs, and 1 French franc buys 0.16 in U.S. dollars, then an arbitrage trader can start with $1.00 and earntex2html_wrap_inline29 dollars thus earning a profit of 6.4 percent.
You will write a program that determines whether a sequence of currency exchanges can yield a profit as described above.
To result in successful arbitrage, a sequence of exchanges must begin and end with the same currency, but any starting currency may be considered.

The Input

The input file consists of one or more conversion tables. You must solve the arbitrage problem for each of the tables in the input file.
Each table is preceded by an integer n on a line by itself giving the dimensions of the table. The maximum dimension is 20; the minimum dimension is 2.
The table then follows in row major order but with the diagonal elements of the table missing (these are assumed to have value 1.0). Thus the first row of the table represents the conversion rates between country 1 and n-1 other countries, i.e., the amount of currency of country i ( tex2html_wrap_inline37 ) that can be purchased with one unit of the currency of country 1.
Thus each table consists of n+1 lines in the input file: 1 line containing n and n lines representing the conversion table.

The Output

For each table in the input file you must determine whether a sequence of exchanges exists that results in a profit of more than 1 percent (0.01). If a sequence exists you must print the sequence of exchanges that results in a profit. If there is more than one sequence that results in a profit of more than 1 percent you must print a sequence of minimal length, i.e., one of the sequences that uses the fewest exchanges of currencies to yield a profit.

Because the IRS (United States Internal Revenue Service) notices lengthy transaction sequences, all profiting sequences must consist of n or fewer transactions where n is the dimension of the table giving conversion rates. The sequence 1 2 1 represents two conversions.
If a profiting sequence exists you must print the sequence of exchanges that results in a profit. The sequence is printed as a sequence of integers with the integer i representing the tex2html_wrap_inline51 line of the conversion table (country i). The first integer in the sequence is the country from which the profiting sequence starts. This integer also ends the sequence.
If no profiting sequence of n or fewer transactions exists, then the line
no arbitrage sequence exists
should be printed.

Sample Input


3
1.2 .89
.88 5.1
1.1 0.15
4
3.1    0.0023    0.35
0.21   0.00353   8.13 
200    180.559   10.339
2.11   0.089     0.06111
2
2.0
0.45

Sample Output


1 2 1
1 2 4 1
no arbitrage sequence exists

出處: http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=3&page=show_problem&problem=40


問題敘述

此問題首先會輸入國家的數量,並且輸入每個國家和每個國家的匯率兌換表(同個國家的兌換匯率不會輸入,因為一定是1),要您求出可以經由最少的兌換次數來達到獲利超過1%的兌換方式(可以在任何國家的幣紙上獲利),若有兩種以上最少兌換次數可以獲利超過1%,隨便印出一種兌換方法即可,若找不到兌換方法,則印出"no arbitrage sequence exists"。

解題思路

此題使用Dynamic programming來解,首先求出各個國家只兌換1次可以得到的最佳解(也就是初始的匯率兌換表),再利用只兌換1次的最佳解求出只兌換2次的最佳解,接著再利用只兌換2次的最佳解求出只兌換3次的最佳解...依此類推,一直求到有國家自己兌換自己可以超過1.01就表示找到解了,若一直求到n次兌換都找不到解,表示此解找不到。

c++ 程式碼

#include <iostream>
#include <vector>
using namespace std;

void findPath(vector< vector< vector<int> > >& path, int startCountry, int endCountry, int step) {
    if (path[step][startCountry][endCountry] != -1) {
        findPath(path, startCountry, path[step][startCountry][endCountry], step - 1);
        cout << " " << path[step][startCountry][endCountry] + 1;
    }
}

int main() {
    int countryNum;

    while (cin >> countryNum) {
         vector< vector< vector<double> > > bestRateEachStep(countryNum, vector< vector<double> >(countryNum,
                      vector<double>(countryNum, 0)));
         vector< vector< vector<int> > > path(countryNum, vector< vector<int> >(countryNum,
                      vector<int>(countryNum, -1)));
         int startCountry = -1;
         int lastStep = -1;

        for (int i=0; i<countryNum; i++) {
            for (int j=0; j<countryNum; j++) {
                if (i == j)
                    bestRateEachStep[0][i][j] = 1;
                else
                    cin >> bestRateEachStep[0][i][j];
            }
        }
        for (int step=1; step<countryNum; step++) {
            for (int k=0; k<countryNum; k++) {
                for (int i=0; i<countryNum; i++) {
                    for (int j=0; j<countryNum; j++) {
                        if (bestRateEachStep[step - 1][i][k] * bestRateEachStep[0][k][j] > bestRateEachStep[step][i][j]) {
                            bestRateEachStep[step][i][j] = bestRateEachStep[step - 1][i][k] * bestRateEachStep[0][k][j];
                            path[step][i][j] = k;
                        }
                    }
                }
            }
            for (int i=0; i<countryNum; i++) {
                if (bestRateEachStep[step][i][i] > 1.01) {
                    startCountry = i;
                    lastStep = step;
                    break;
                }
            }
            if (startCountry != -1)
                break;
        }
        if (startCountry == -1)
            cout << "no arbitrage sequence exists\n";
        else {
            cout << startCountry + 1;
            findPath(path, startCountry, startCountry, lastStep);
            cout << " " << startCountry + 1 << endl;
        }
    }

    return 0;
}

2013年8月31日 星期六

103 - Stacking Boxes


Background

Some concepts in Mathematics and Computer Science are simple in one or two dimensions but become more complex when extended to arbitrary dimensions. Consider solving differential equations in several dimensions and analyzing the topology of an n-dimensional hypercube. The former is much more complicated than its one dimensional relative while the latter bears a remarkable resemblance to its ``lower-class'' cousin.

The Problem

Consider an n-dimensional ``box'' given by its dimensions. In two dimensions the box (2,3) might represent a box with length 2 units and width 3 units. In three dimensions the box (4,8,9) can represent a box tex2html_wrap_inline40 (length, width, and height). In 6 dimensions it is, perhaps, unclear what the box (4,5,6,7,8,9) represents; but we can analyze properties of the box such as the sum of its dimensions.
In this problem you will analyze a property of a group of n-dimensional boxes. You are to determine the longest nesting string of boxes, that is a sequence of boxes tex2html_wrap_inline44 such that each box tex2html_wrap_inline46 nests in box tex2html_wrap_inline48 ( tex2html_wrap_inline50 .
A box D = ( tex2html_wrap_inline52 ) nests in a box E = ( tex2html_wrap_inline54 ) if there is some rearrangement of the tex2html_wrap_inline56 such that when rearranged each dimension is less than the corresponding dimension in box E. This loosely corresponds to turning box D to see if it will fit in box E. However, since any rearrangement suffices, box D can be contorted, not just turned (see examples below).
For example, the box D = (2,6) nests in the box E = (7,3) since D can be rearranged as (6,2) so that each dimension is less than the corresponding dimension in E. The box D = (9,5,7,3) does NOT nest in the box E = (2,10,6,8) since no rearrangement of D results in a box that satisfies the nesting property, but F = (9,5,7,1) does nest in box E since F can be rearranged as (1,9,5,7) which nests in E.
Formally, we define nesting as follows: box D = ( tex2html_wrap_inline52 ) nests in box E = ( tex2html_wrap_inline54 ) if there is a permutation tex2html_wrap_inline62 of tex2html_wrap_inline64such that ( tex2html_wrap_inline66 ) ``fits'' in ( tex2html_wrap_inline54 ) i.e., if tex2html_wrap_inline70 for all tex2html_wrap_inline72 .

The Input

The input consists of a series of box sequences. Each box sequence begins with a line consisting of the the number of boxes k in the sequence followed by the dimensionality of the boxes, n (on the same line.)
This line is followed by k lines, one line per box with the n measurements of each box on one line separated by one or more spaces. The tex2html_wrap_inline82 line in the sequence ( tex2html_wrap_inline84 ) gives the measurements for the tex2html_wrap_inline82 box.
There may be several box sequences in the input file. Your program should process all of them and determine, for each sequence, which of the kboxes determine the longest nesting string and the length of that nesting string (the number of boxes in the string).
In this problem the maximum dimensionality is 10 and the minimum dimensionality is 1. The maximum number of boxes in a sequence is 30.

The Output

For each box sequence in the input file, output the length of the longest nesting string on one line followed on the next line by a list of the boxes that comprise this string in order. The ``smallest'' or ``innermost'' box of the nesting string should be listed first, the next box (if there is one) should be listed second, etc.
The boxes should be numbered according to the order in which they appeared in the input file (first box is box 1, etc.).
If there is more than one longest nesting string then any one of them can be output.

Sample Input


5 2
3 7
8 10
5 2
9 11
21 18
8 6
5 2 20 1 30 10
23 15 7 9 11 3
40 50 34 24 14 4
9 10 11 12 13 14
31 4 18 8 27 17
44 32 13 19 41 19
1 2 3 4 5 6
80 37 47 18 21 9

Sample Output


5
3 1 2 4 5
4
7 2 5 6


出處: http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=39


問題描述

此問題首先會給您盒子的數量,並且告訴您盒子的維度(會超過三維),接著會輸入每一個盒子的邊長,最後要您求出最多可以疊多少個盒子,並且印出疊最多盒子的串列。

解題思路

首先如何判斷盒子A是否可以塞進盒子B裡呢? 根據題目的定義,只要盒子A有任何一種邊長的排列方式可以使得盒子A所有的邊長都比盒子B的邊長來得小,就可以將盒子A塞進盒子B裡。最直接的判斷方法就是把所有盒子的邊長都先排序好,最後只需要從第一個邊長逐一比較到最後一個邊長,若盒子A的每個邊長皆比盒子B來得小,那就表示盒子A可以塞進盒子B裡,否則就不行。至於如何找出可以疊最多盒子的串列呢? 其實此問題可以用dynamic programming的方法來解,也就是使用類floyd-warshall的演算法,把每個盒子視為一個點,把可以疊的盒子數量視為路徑長,最後只需把求最短路徑改成求最長路徑即可。

c++程式碼

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void findPath(vector< vector<int> >& path, int startVertex, int endVertex) {
    if (path[startVertex][endVertex] != -1) {
        findPath(path, startVertex, path[startVertex][endVertex]);
        cout << " " << path[startVertex][endVertex] + 1;
        findPath(path, path[startVertex][endVertex], endVertex);
    }
}

int main() {
    int boxNum, dimens;

    while (cin >> boxNum >> dimens) {
        vector< vector<int> > boxes(boxNum, vector<int>(dimens, 0));
        vector< vector<int> > pathLength(boxNum, vector<int>(boxNum, 0));
        vector< vector<int> > path(boxNum, vector<int>(boxNum, -1));
        int maxPathLength = -1;
        int startVertex = -1;
        int endVertex = -1;

        for (int i=0; i<boxes.size(); i++) {
            for (int j=0; j<boxes[i].size(); j++)
                cin >> boxes[i][j];
            sort(boxes[i].begin(), boxes[i].end());
        }
        for (int i=0; i<boxes.size(); i++) {
            for (int j=0; j<boxes.size(); j++) {
                bool isNest = true;

                for (int k=0; k<boxes[i].size(); k++) {
                    if (boxes[i][k] >= boxes[j][k]) {
                        isNest = false;
                        break;
                    }
                }
                if (isNest)
                    pathLength[i][j] = 1;
                if (pathLength[i][j] > maxPathLength) {
                    maxPathLength = pathLength[i][j];
                    startVertex = i;
                    endVertex = j;
                }
            }
        }
        for (int k=0; k<boxes.size(); k++) {
            for (int i=0; i<boxes.size(); i++) {
                for (int j=0; j<boxes.size(); j++) {
                    if (pathLength[i][k] != 0 && pathLength[k][j] != 0 && pathLength[i][k] + pathLength[k][j] > pathLength[i][j]) {
                        pathLength[i][j] = pathLength[i][k] + pathLength[k][j];
                        path[i][j] = k;
                        if (pathLength[i][j] > maxPathLength) {
                            maxPathLength = pathLength[i][j];
                            startVertex = i;
                            endVertex = j;
                        }
                    }
                }
            }
        }
        cout << maxPathLength + 1 << endl;
        cout << startVertex + 1;
        if (maxPathLength) {
            findPath(path, startVertex, endVertex);
            cout << " " << endVertex + 1 << endl;
        }
        else
            cout << endl;
    }

    return 0;
}

2013年8月23日 星期五

10940 - Throwing cards away II

Given is an ordered deck of n cards numbered 1 to n with card 1 at the top and card n at the bottom. The following operation is performed as long as there are at least two cards in the deck:
Throw away the top card and move the card that is now on the top of the deck to the bottom of the deck.
Your task is to find the last, remaining card.
Each line of input (except the last) contains a positive number n ≤ 500000. The last line contains 0 and this line should not be processed. For each number from input produce one line of output giving the last remaining card. Input will not contain more than 500000 lines.

Sample input

7
19
10
6
0

Output for sample input

6
6
4
4

出處: http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1881



此題的操作具有遞迴關係。首先2張牌的情況,經過題目所說的操作答案就是第2張牌,至於3張牌的情況,經過題目所說的操作之後會變成2張牌的情況,4張牌的情況經過操作之後會變成3張牌的情況 ... n張牌的情況經過操作會變成n - 1張牌的情況。所以每個情況的解都可以由前一個情況來推算,利用dynamic programming的觀念,先把2到500000張牌的每個情況都推算出來並且記起來,接下來就看題目要的是哪個情況就把哪個情況的解叫出來就好了。至於如何從n - 1張牌情況的解推出n張牌情況的解呢?若n - 1張牌情況的解是最後一張,則n張牌情況的解就是第2張,若n - 1張情況的解不是最後一張,那這個解的位置加上2就是n張牌情況的解。此題還有一點要注意的是若輸入是1則輸出就是1,因為無法進行任何操作。

c++ code:

#include <iostream>
#include <map>
using namespace std;

int main() {
    map<int, int> table;

    table[1] = 1;
    table[2] = 2;
    for (int i=3; i<=500000; i++) {
        if (table[i - 1] == i - 1)
            table[i] = 2;
        else
            table[i] = table[i - 1] + 2;
    }
    while (true) {
        int n;

        cin >> n;
        if (!n)
            break;
        cout << table[n] << endl;
    }

    return 0;
}