题解|《算法竞赛进阶指南》 Sticks

题目描述
George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were originally. Please help him and design a program which computes the smallest possible original length of those sticks. All lengths expressed in units are integers greater than zero.
输入描述:
The input contains blocks of 2 lines. The first line contains the number of sticks parts after cutting, there are at most 64 sticks. The second line contains the lengths of those parts separated by the space. The last line of the file contains zero.
输出描述:
The output should contains the smallest possible length of original sticks, one per line.

思路
1.因为不知道原始木棒的长度len,但是知道每根小木棍的长度,小木棒最长的时候就是一根的时候也就是长度等于所有的小木棍的长度总和sum。所以,我们可以枚举长度len。这样子就把查询问题转化成了判断该长度是否可行的问题。
2.在长度确定的同时也就确定了小木棒的数量cnt=sum/len,那么这个就可以作为合法标志的判断条件:在所有的小木棍都用完的情况下拼成了cnt个长度相等的小木棒。
3.这个时候就要思考这道题要让计算机做什么?
1.枚举长度len;
2.用之前还没有使用过的小木棍拼凑小木棒;
3.判断该长度方案是否可行。
4.考虑上面的那些状态是需要在搜索中考虑到的?
每根小木棒的长度len; 已经拼成了多少根小木棒; 每一根小木棍的状态
因为上面的sum最大是64*50(这个数字还是蛮大的),那么最坏的打算是每一个长度都要考虑进去,每一个长度的每一种拼法也都要尝试一遍,这样的话最后得出答案的话就是会需要很长时间的,更何况给出的数据是多组数据,所以我们要考虑剪枝优化。

剪枝
1.搜索顺序的优化:我们可以按照小木棍的长度进行排序,从大到小,若填上最长的之后没有可以匹配的话,那么这个长度绝对是不合法的。(大块一定比小块需要搜索的次数少)
2.跳过相同木棒
3.如果是第一个木棒失败,则一定失败
4.如果是最后一个木棒失败,则一定失败

完整C++版AC代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 70;
int n;
int sum, length;
int stricks[N];
bool st[N];
//u为当前第几段了,cur记录当前段的长度,starct为上一次已经选过的值
bool dfs(int u, int cur, int start)
{
    if (u * length == sum) return true;
    if (cur == length)return dfs(u + 1, 0, 0);//如果发现这一段已经用完了,下一段接着来.

    for (int i = start; i < n; i++)
    {
        if (st[i]) continue;
        int l = stricks[i];
        if (cur + l <= length)
        {
            st[i] = true;
            if (dfs(u, cur + l, i + 1)) return true;
            st[i] = false;

            if (!cur) return false; // 剪枝3 如果是第一个木棒失败,则一定失败

            if (cur + l == length) return false;// 剪枝4 如果是最后一个木棒失败,则一定失败
             // 剪枝2 跳过相同木棒
            int j = i;
            while (j < n && stricks[j] == l) j++;
            i = j - 1;
        }
    }
    return false;
}

int main() {
    ios::sync_with_stdio;

    while (cin >> n, n) {

        sum = 0, length = 0;
        memset(st, false, sizeof st);

        for (int i = 0; i < n; i++) {
            cin >> stricks[i];
            if (stricks[i] > 50) continue;
            sum += stricks[i];
            length = max(length, stricks[i]);
        }

        sort(stricks, stricks + n); // 剪枝:优化搜索顺序
        reverse(stricks, stricks + n);

        for (int i = 0; i < n; i++)
            if (stricks[i] > 50)
                st[i] = true;

        while (true)
        {
            if (sum % length == 0 && dfs(0, 0, 0))
            {
                cout << length << endl;
                break;
            }
            length++;
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Mashiro-zBlog/p/11439298.html

时间: 2024-10-04 05:54:48

题解|《算法竞赛进阶指南》 Sticks的相关文章

《算法竞赛进阶指南》学习总结 #include&lt;algorithm&gt;

今天下午大致学完了进阶指南中algorithm头文件下的内容,在这里进行一个总结.   reverse翻转   顾名思义,reverse进行的操作就是翻转原来的顺序,理解非常简单,故不赘述. 操作样例: #include<bits/stdc++.h> using namespace std; vector<int>a; int b[233]; int main() { int na,nb; //vector的实现 scanf("%d",&na); for

《算法竞赛进阶指南》学习总结 二分与三分

首先......我是一个很菜很菜的萌新,所以这篇文章写得很详细,有很多我自己的口水话方便我理解,请各位谨慎食用qwq 以前在网上找过很多介绍二分的博客,但都感觉对萌新不太友好,反正我当时连跳石头都没看懂,所以决定自己写一篇!其中有我的想法,也借鉴了书里的很多内容,感谢lyd. 二分答案,顾名思义,就是对我们所需要的答案进行二分,对我们要求的值进行二分.二分的基础用法是在单调序列或者单调函数当中查找,当答案具有单调性,我们就可以采用二分来计算,当然还有三分,在后面我会详细讲到 整数集合上的二分 在

《算法竞赛进阶指南》刷题记录

总算闲下来一些辣!然后最近发现其实看书是真真很有效但是一直没有落实!所以决定落实一下这段时间把这本书看完题目做完! 然后发现还有挺多题目挺巧妙的于是一堆博客预警,,,可能最近会写很多比较水(但是我还是不会做)的题目的题解 先放个空壳子晚上再来写qwq 原文地址:https://www.cnblogs.com/lqsukida/p/9988445.html

《算法竞赛进阶指南》0.8总结与练习(1)

116. 飞行员兄弟 "飞行员兄弟"这个游戏,需要玩家顺利的打开一个拥有16个把手的冰箱. 已知每个把手可以处于以下两种状态之一:打开或关闭. 只有当所有把手都打开时,冰箱才会打开. 把手可以表示为一个4х4的矩阵,您可以改变任何一个位置[i,j]上把手的状态. 但是,这也会使得第i行和第j列上的所有把手的状态也随着改变. 请你求出打开冰箱所需的切换把手的次数最小值是多少. 输入格式 输入一共包含四行,每行包含四个把手的初始状态. 符号"+"表示把手处于闭合状态,而

算法竞赛进阶指南做题记录

基本算法 递归与递推 费解的开关 Strange Towers of Hanoi Sumdiv Fractal Streets 前缀和与差分 激光炸弹 IncDec Sequence Tallest Cow 二分 Best Cow Fences 排序 Cinema 货舱选址 七夕祭 Running Median 第K大数 Ultra-QuickSort 奇数码问题 原文地址:https://www.cnblogs.com/Maktub-blog/p/11009723.html

算法竞赛进阶指南 走廊泼水节

原题链接 题目描述 给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树. 求增加的边的权值总和最小是多少. 输入格式 第一行包含整数t,表示共有t组测试数据. 对于每组测试数据,第一行包含整数N. 接下来N-1行,每行三个整数X,Y,Z,表示X节点与Y节点之间存在一条边,长度为Z. 输出格式 每组数据输出一个整数,表示权值总和最小值. 每个结果占一行. 数据范围 \(N \le 6000,Z \le 100\) 输入样例: 2 3 1 2 2 1

bzoj 1787 &amp;&amp; bzoj 1832: [Ahoi2008]Meet 紧急集合(倍增LCA)算法竞赛进阶指南

题目描述 原题连接 Y岛风景美丽宜人,气候温和,物产丰富. Y岛上有N个城市(编号\(1,2,-,N\)),有\(N-1\)条城市间的道路连接着它们. 每一条道路都连接某两个城市. 幸运的是,小可可通过这些道路可以走遍Y岛的所有城市. 神奇的是,乘车经过每条道路所需要的费用都是一样的. 小可可,小卡卡和小YY经常想聚会,每次聚会,他们都会选择一个城市,使得3个人到达这个城市的总费用最小. 由于他们计划中还会有很多次聚会,每次都选择一个地点是很烦人的事情,所以他们决定把这件事情交给你来完成. 他们

《算法竞赛进阶指南》打卡活动 #0x00 基本算法

101. 最高的牛 题目链接:https://www.acwing.com/problem/content/103/ 作为一个银牌水平的主演数据结构的演员来说,这题现在发现非常好想,每个牛分配一个优先度.我搞一个区间线段树,每次update中间一段使得他们的优先度整体下降到比两端中较小的优先度还要小.然后反过来按照优先度分配身高.哈哈!根本不需要什么算法.不过每次都不询问为什么要线段树呢?差分不香吗?当然不香,每次询问当前的优先度啊! const int MAXN = 10000; int re

【算法竞赛进阶指南】USACO07Tallest Cow

前缀和,利用左右端点操作代替对区间的操作,从而优化输入,最后进行一次前缀和的操作,求得结果,这道题里面有个很关键的问题,就是需要去重,本来我想用set,但貌似有点鬼畜,算了,利用map去重,还有pair类型(学一下) #include <iostream> #include <algorithm> #include <map> #include <utility> using namespace std; map<pair<int,int>