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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值