poj 1011 sticks

简单来说就是有很多长度相同的木棍,被切分为不同长度,然后要求重新拼起来,看拼起来之后,长度相同的木棍,最短是多少长度。

这里看到几个问题

  1. 拼起来之后长度应该是一样的
  2. 从条件一推断,最段长度minL应该满足total_len % minL == 0,即最小长度可以整除总长度
  3. 从结论二可以推断,拼好之后最短长度应该大于所有木棍里面最长那根木棍
  4. 同样从条件一推断,最大长度是所有木棍的总长度,也就是只能拼成一根
  5. 注意,同样存在一次都不用拼的情况。
  6. 如果可以拼好,前面的所有木棍都拼完的时候,最后一根木棍是不需要拼的,因为肯定是那么长

然后进一步,为了拼出木棍,有什么好的拼法呢?一个考虑是,将所有木棍按从大到小排序,然后进行拼,这样可以在比较接近根的地方剪掉尽可能的情况。这样就能推理出以下几个剪枝:

  1. 只需要检查可以被总长度整除的长度
  2. 只需需要检查大于等于最长单根木棍的长度
  3. 对于长度等于正在尝试的长度的木棍,直接认为已经拼好,因为不管这根木棍接哪里,都是非法的
  4. 最后一根木棍不用拼
  5. 如果有一根木棍,接上去之后刚好构成需要测试的长度,那就应该接上。这是基于以下推理:如果我们换一根更长的,显然,超出我们测试的长度,非法。如果换一根更短的,即使后面这根更短的,能拼出一根相同的长度,其效果也跟使用这根长的一样,并且由于后面更短更灵活,浪费了他补齐的功能。
  6. 如果在开始拼一根长木棍的时候,第一根拼上去,发现不能继续拼了,应该直接放弃继续拼,因为不论如何,后面都存在无法将这根木棍拼进去的情况。
  7. 如果某个长度的木棍拼上去,没有可行解,那应该跳过同长度的木棍
#include <stdlib.h>
#include <string.h>
#include <iostream>
using namespace std;

int sticks[70];
bool is_used[70];

bool dfs(int len, int index, int res, int total_len, int checking_len)
{
    if(checking_len == total_len)
    {
        //剩余的总长度跟检查长度相同,这是最后一根了,不用检查,直接成功
        return true;
    }
    for(int i = index; i < len; i++)
    {
        if(is_used[i])
        {
            continue;
        }
        if(sticks[i] > res)
        {
            //太长,显然剪掉
            continue;
        }
        is_used[i] = true;
        if(res == sticks[i])
        {
            //用掉之后刚好开始拼新一根
            if(dfs(len, 0, checking_len, total_len - sticks[i], checking_len))
            {
                return true;
            }
        }
        //just use it
        else if(dfs(len, i, res - sticks[i], total_len - sticks[i], checking_len))
        {
            return true;
        }

        is_used[i] = false;

        //剪枝:这是第一根拼上来的木棍,说明这个方案不成立,回去吧
        if(res == checking_len)
        {
            return false;
        }
        //剪枝:不搜索相同长度的棍子
        while(sticks[i] == sticks[i+1])i++;
    }
    return false;
}

int cmp(const void* a, const void* b)
{
    return *(int*)b - *(int*)a;
}

int cal(int len, int max_len, int max_one_len)
{
    //排序剪枝
    qsort(sticks, len, sizeof(int), cmp);
    //从单根最长开始尝试
    for(int i = max_one_len; i < max_len; i++)
    {
        if(max_len % i == 0)
        {
            memset(is_used,0,sizeof(is_used));

            //将跟当前尝试长度相同的,直接标志为已用
            int stick_total_len = max_len;
            for (int j = 0; j < len; ++j) {
                if(sticks[j] == i)
                {
                    is_used[j] = true;
                    stick_total_len -= i;
                }
            }
            //注意有所有木棍都被用完的情况
            if(stick_total_len == 0)
            {
                //all sticks are in same size
                return i;
            }
            //开始测试
            if(dfs(len, 0, i, stick_total_len, i))
            {
                return  i;
            }
        }
    }
    //最坏情况,所有木棍拼一起
    return max_len;
}

int main(int argc, char *argv[])
{
    int n = 0 ;
    while(1)
    {
        cin >> n;
        if(n == 0)
        {
            break;
        }
        memset(sticks, 0, sizeof(sticks));
        int max_len = 0;
        int max_one_len = 0;

        for (int i = 0; i < n; ++i) {
            cin >> sticks[i];
            max_len += sticks[i];
            max_one_len = std::max(max_one_len, sticks[i]);
        }
        cout << cal(n, max_len, max_one_len) << endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zerooffdate/article/details/81587591