P1120 小木棍 [数据加强版](搜索 剪枝)

原题: https://www.luogu.org/problemnew/show/P1120

题意:

有n(<=65)根小木棍,长度 a i a_i ai(<=50),这些小木棍是由x根L长木棍切割而得,现在x和L不确定,让你求最小的L。

题目说了如果出现大于50的木棍要忽略。

解析:

首先当然是要枚举长度L了,下界为给出长度最大值 m a ma ma,上界为所有木棍长度和 s u m sum sum。当然 s u m sum sum是一定成立的,所以枚举到 s u m / 2 sum/2 sum/2即可。

开始的时候,我有点犯傻了,以为只要数据可以组合,贪心组合即可,用链表存状态的更新情况,用数组维护剩余的棍子,一发牛b哄哄的代码WA了。

#include
using namespace std;
#define debug(i) printf(" #  %d\n",i)
int a[66], sum, ma;

bool vis[3255];
struct state {
    int pre, num;
} e[3255];
int sta[3255], co;

void Erase(int S[], int &n, int Len) {
    stack<int>T;
    int now = Len;
    while(now > 0) {
        T.push(e[now].num);
        now = e[now].pre;
    }
    int j = 0;
    for(int i = 1; i <= n; i++) {
        if(!T.empty() && i == T.top()) {
            T.pop();
            continue;
        }
        S[++j] = S[i];
    }
    n = j;
}

bool cmp(int a, int b) {
    return a > b;
}
int main() {
    int n;
    scanf("%d", &n);
    sum = 0;
    ma = -1;
    for(int i = 1; i <= n; i++) {
        int tmp;
        scanf("%d", &tmp);
        if(tmp > 50) {
            n--;
            i--;
            continue;
        }
        a[i] = tmp;
        sum += tmp;
        ma = max(ma, tmp);
    }
    sort(a + 1, a + 1 + n, cmp);
    int ans = sum;
    for(int Len = ma; Len <= sum / 2; Len++) { // 枚举长度
        debug(Len);
        if(sum % Len)
            continue;
        int num = sum / Len; // 段数

        int S[66]; // 存剩下的棍子
        int top = n;
        for(int i = 1; i <= n; i++)
            S[i] = a[i];
        bool cant = 0;
        while(num--) {
            bool succ = 0;
            OUT(S, top);
            memset(vis, 0, sizeof(vis));
            co = 1;
            vis[0] = 1;
            sta[1] = 0;
            for(int i = 1; i <= top; i++) {
                int nowco = co;
                for(int j = 1; j <= nowco; j++) {
                    int to = sta[j] + S[i];
                    if(to > Len || vis[to])
                        continue;
                    // to这个状态由sta[j]加上剩下数组中第i个棍子得到
                    e[to].num = i;
                    e[to].pre = sta[j];
                    sta[++co] = to;
                    vis[to] = 1;
                    if(to == Len) {
                        Erase(S, top, Len);
                        succ = 1;
                        break;
                    }
                }
                if(succ)
                    break;
            }
            if(!succ) {
                cant = 1;
                break;
            }
        }
        if(!cant) {
            ans = Len;
            break;
        }
    }
    printf("%d\n", ans);
}

后来猛地想起来,选取策略是有影响的,比如用 3   3   3   3   1111 3 \,3\, 3\, 3\, 1 1 1 1 33331111来拼4个4,如果先选1111,那么就不能拼了。


所以只能暴搜了,dfs(int Len, int needLen, int needNum, int up)表示设置原长度为Len,当前棍子还需要拼needLen,除了当前棍子还需要拼needNum根,下一个选取来凑的棍子从up开始往下。

说说需要注意的事:

  1. 用桶来装会比较方便,用a[Len]--就可以维护剩余木棍。
  2. 从大的木棍的开始选,因为前面一堆小的堆起来,搜索失败后回溯的过程会非常庞大。
  3. 因为这题只需要输出一个答案即可,而且我们对原长度从小到大判断,所以只要遇到一个可以的一定是最优解,那么我们输出后ext(0)结束程序就不用回溯了。
  4. up维护的是已选择木棍中的最小值。因为从大到小选,所以下一个选的木棍一定不好比上一个大。
  5. 在dfs中枚举下一个选择的木棍时,从大到下,大的那边应该从up和needLen的小值开始,小的那边到所有木棍的最小值结束。
  6. 重要点: 如果刚开始选择木棍,即needLen==Len,那么选完后就return,因为这个木棍一定会被选。即第一个选什么并不重要。
  7. 重要点: 如果选择的木棍长度和需要的长度相同,还不行,那就return。因为两个1一定比一个2用起来灵活。
#include
using namespace std;

int a[100], n;
int ma, mi, sum;

void dfs(int Len, int needLen, int needNum, int up) {
    if(needNum == 0) {
        printf("%d\n", Len);
        exit(0);
    }
    if(needLen == 0) {
        dfs(Len, Len, needNum - 1, ma);
        return;
    }
    for(int i = min(up, needLen); i >= mi; i--) {
        if(a[i]) {
            a[i]--;
            dfs(Len, needLen - i, needNum, i);
            a[i]++;
            if(needLen == Len || needLen == i)
                return ;
        }
    }
}

int main() {
    memset(a, 0, sizeof(a));
    scanf("%d", &n);
    mi = 1e9, ma = -1, sum = 0;
    for(int i = 1; i <= n; i++) {
        int tmp;
        scanf("%d", &tmp);
        if(tmp > 50)
            continue;
        a[tmp]++;
        ma = max(ma, tmp);
        mi = min(mi, tmp);
        sum += tmp;
    }
    int ans = sum;
    for(int i = ma; i <= sum / 2; i++) {
        if(sum % i)
            continue;
        dfs(i, i, sum / i, ma);
    }
    printf("%d\n", ans);
}

你可能感兴趣的:(图论/搜索)