1. 程式人生 > >POJ 1010 1020 DFS+剪枝

POJ 1010 1020 DFS+剪枝

之所以要寫這兩題的解題報告,原因就是這兩題是比較好的搜尋題,必須記錄一下。

另外,最近做題狀態也不行,看見題目後沒想法,然後看別人解題報告了,突然發現自己想過這方面,但是沒深入去想,然後就做不出來了。

1.POJ 1010  題意晦澀難懂。從網上找了一個稍微能說清楚的題意

 題意:
  給出n種郵票,每種郵票有自己的面值(面值可能重複)
  指定m種“總面值”,對每種“總面值”,求解滿足如下條件的組合以達到該“總面值”
(1) 所用郵票在n種中可以重複選取
(2) 所用郵票張數〈=4
(3) 儘量多的使用那個不同種類的郵票 Max (Stamp Types)
(4) 若有多種方案滿足(3),則選取張數最小的一種方案 Min (Stamp Num)
(5) 若有多種方案滿足(3)(4),則選取“最大面額”最高的一種方案。 Max(Heightest Value)
(6) 若有多種方案滿足(3)(4)(5) 則輸出 “tie” 1010

並且這題給的sample都還夾著註釋,不仔細看題還以為那些也需要輸入。

說一下思路吧,動態規劃是最優選擇,但是確實不好想,普遍的都是用DFS。

優化之一: 每種面值的郵票最多5種即可,如果有多的,就不需要了。比如一下子給了1 1 1 1 1 1 1 1,八個1,其實只需要5個就行,因為題目說了,最多4個,保留5個的目的就是為了多解準備的。再多也沒意義。這樣由於最多有25種郵票,也就是陣列到126就夠了。

然後dfs過程中如果選擇的郵票面額之和大於所需的,也剪掉,總數大於4的剪掉,剩下的貌似就沒什麼了。

dfs過程中,有一箇中間值,儲存著郵票的種類,總個數,最大面值,所選的郵票等,然後有一個最終結果,每次dfs出的中間值,如果優於最終結果就要更新最終結果,那麼怎樣算是tie呢,就要用1個變數標記一下,如果被更新了,就置這個變數為1,如果出現打平的,就把他++一下,這樣不斷的覆蓋這個變數即可。

另外,這道題目貌似給的序列都是有序的。

/*
ID: sdj22251
PROG: subset
LANG: C++
*/
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <map>
#include <string>
#include <cstring>
#include <cmath>
#include <ctime>
#define MAXN 100007
#define INF 1000000000
#define eps 1e-7
using namespace std;
int size, x;
int stamp[550];
int some;
struct wwj
{
    int cnt, num;
    int mx, ana[555];
    void init()
    {
        cnt = 0;
        num = 0;
        mx = 0;
        memset(ana, 0, sizeof(ana));
    }
}tmp, ans;
void init()
{
    some = 0;
    tmp.init();
    ans.init();
}
void get()
{
    tmp.cnt = 0;
    tmp.mx = 0;
    for(int i = 0; i < size; i++)
    {
        if(tmp.ana[i])
        {
            tmp.cnt++;
            if(stamp[i] > tmp.mx)
            tmp.mx = stamp[i];
        }
    }
}
void dfs(int index, int num, int sum)
{
    if(num > 4) return;
    if(sum == x)
    {
        tmp.num = num;
        get();
        if(tmp.cnt > ans.cnt)
        {
            ans = tmp;
            some = 1;
        }
        else if(tmp.cnt == ans.cnt)
        {
            if(tmp.num < ans.num)
            {
                ans = tmp;
                some = 1;
            }
            else if(tmp.num == ans.num)
            {
                if(tmp.mx > ans.mx)
                {
                    ans = tmp;
                    some = 1;
                }
                else if(tmp.mx == ans.mx) {some++;}
            }
        }
        return;
    }
    if(index >= size || sum > x) return;
    for(int i = 0; i <= 4; i++)
    {
        tmp.ana[index] += i;
        dfs(index + 1, num + i, sum + stamp[index] * i);
        tmp.ana[index] -= i;
    }
}
void output()
{
    printf("%d ", x);
    if(some == 0) printf("---- none\n");
    else if(some == 1)
    {
        printf("(%d):", ans.cnt);
        for(int i = 0; i < size; i++)
        {
            if(ans.ana[i] > 0)
            {
                for(int j = 0; j < ans.ana[i]; j++)
                    printf(" %d", stamp[i]);
            }
        }
        printf("\n");
    }
    else printf("(%d): tie\n", ans.cnt);
}
int main()
{
    while(scanf("%d", &x) != EOF)
    {
        size = 0;
        stamp[size++] = x;
        while(scanf("%d", &x) != EOF && x)
        {
            int nt = 0;
            for(int i = 0; i < size; i++)
                if(stamp[i] == x) nt++;
            if(nt >= 5) continue;
            stamp[size++] = x;
        }
        while(scanf("%d", &x) != EOF && x)
        {
            init();
            dfs(0, 0, 0);
            output();
        }
    }
    return 0;
}


2.poj 1020 

又是一道搜尋題

題目大意是給出一個大的正方形,然後又給出了一群小正方形,問大的正方形能不能由這些小正方形組成,並且每個正方形都要用到。

思路是貪心+dfs

首先,對輸入的陣列,由於題目中說了,小正方形的邊長範圍是1~10,所以開個陣列記錄一下每個邊長的正方形個數,dfs的時候列舉就方便了。

然後用一個數組,len[i]表示第i行被覆蓋了len[i]列,而且是左邊無空隙的。

然後每次貪心找最小的len[i],因為這樣的話,達到最優解的可行性最大。在這個位置上,看下面和右面是否有足夠的位置,前提是左邊是填充好的,所以就要求這些位置的len[i]必須都是一樣的,然後就列舉邊長,往裡塞小正方形,找不到解就回溯。

網上有的貪心是不正確的,因為沒有考慮到一些比較特別的資料,而且poj的這道題資料還很弱。正確的做法應該是貪心+回溯,就能保證一定要到正確的答案。

/*
ID: sdj22251
PROG: subset
LANG: C++
*/
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <map>
#include <string>
#include <cstring>
#include <cmath>
#include <ctime>
#define MAXN 100007
#define INF 1000000000
#define eps 1e-7
using namespace std;
int n, x;
int len[55], cnt[15];
bool dfs(int deep)
{
    if(deep == n) return true;
    int mi = INF;
    int pos = 0;
    for(int i = 1; i <= x; i++)
    {
        if(mi > len[i])
        {
            mi = len[i];
            pos = i;
        }
    }
    for(int i = 1; i <= 10; i++)
    {
        if(cnt[i])
        {
            if(len[pos] + i <= x)
            {
                int width = 0;
                for(int j = pos; j <= x; j++)
                    if(len[j] == len[pos]) width++;
                    else break;
                if(width >= i)
                {
                    cnt[i]--;
                    for(int j = pos; j < pos + i; j++)
                        len[j] += i;
                    if(dfs(deep + 1)) return true;
                    cnt[i]++;
                    for(int j = pos; j < pos + i; j++)
                        len[j] -= i;
                }
            }
        }
    }
    return false;
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &x);
        memset(len, 0, sizeof(len));
        memset(cnt, 0, sizeof(cnt));
        scanf("%d", &n);
        int area = 0, t;
        for(int i = 0; i < n; i++)
        {
            scanf("%d", &t);
            cnt[t]++;
            area += t * t;
        }
        if(x * x == area && dfs(0))
            puts("KHOOOOB!");
        else puts("HUTUTU!");
    }
    return 0;
}