POJ.1416 Shredding Company (DFS)
POJ.1416 Shredding Company [从零开始DFS(8)]
从零开始DFS
HDOJ.1342 Lotto [从零开始DFS(0)] — DFS思想与框架/双重DFS
HDOJ.1010 Tempter of the Bone [从零开始DFS(1)] —DFS四向搜索/奇偶剪枝
HDOJ(HDU).1015 Safecracker [从零开始DFS(2)] —DFS四向搜索变种
HDOJ(HDU).1016 Prime Ring Problem (DFS) [从零开始DFS(3)] —小结:做DFS题目的关注点
HDOJ(HDU).1035 Robot Motion [从零开始DFS(4)]—DFS题目练习
HDOJ(HDU).1241 Oil Deposits(DFS) [从零开始DFS(5)] —DFS八向搜索/双重for循环遍历
HDOJ(HDU).1258 Sum It Up (DFS) [从零开始DFS(6)] —DFS双重搜索/去重技巧
HDOJ(HDU).1045 Fire Net [从零开始DFS(7)]—DFS练习/check函数的思想
POJ.1416 Shredding Company [从零开始DFS(8)]—DFS练习/sum函数的设计
题意分析
给出一个目标数字n和一串数字序列s,要求切割序列s为若干数字段s1,s2,s3……sn,最后求出最大的 不超过目标数字n的 序列s切割方式,和这些字段的和。若对于最大的数字有多种切割方式,则输出rejected;若没有这样的切割方式,则输出error。
直接对给出的数字串操作有些不方便,我们不妨想想挡板法。对于给出的序列s(若其有x位),则有x-1个空挡。对于每一个空挡,我们有选/不选两种选择,这又回到了讨论过的那种选/不选的模型,可以用dfs暴力枚举来解决此类问题。
题目要求输出这样的分割方式和最大的和,则在dfs函数中一定有更新最大和的部分,并且还要保存这样的分割方式。递归边界就是,如果分割完了,那么就计算一下当前的和,如果和比目前的最大和大并且比目标值小,就更新,否则就舍弃。
分析到这里其实就差不多了,我在做此题时比较伤脑筋的是如何计算这样这样字段的和,还有如何保存起来。想到的解决办法是:
1.用一个数组gap来表示间隔。对于数字序列num[1]num[2]……num[n](num[i]表示的是一位数字),gap[i]表示num[i]和num[i+1]中是否有间隔。若有则为1,否则为0;
2.为便于计算字段和,规定gap[0] = gap[n] = 1(n为给出的字符序列的长度,如12345的长度为5);
3.用save数组表示当前最优的分割方式。
上代码。
代码总览
/*
Title:POJ.1416
Author:pengwill
Date:2017-2-14
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
char nu[100];
int num[100],gap[100],save[100];
int ret,len,test,time= 0;
long long tar;
bool mult = false, judge = false;
void init()
{
memset(gap,0,sizeof(gap));
len = strlen(nu);
for(int i = 0,j = 1; i<len;++i,++j) num[j] = nu[i]-'0';
judge = mult = false;
ret = 0;gap[0] = gap[len] = 1; test = 0;time = 0;
}
int sum()
{
int base = 1; int ans = 0; int t = 0;
for(int i = len ; i>=0; --i){
if(gap[i] == 0){
base*=10;
t += num[i] * base;
}else if(gap[i] == 1){
ans+=t; base = 1;
t = num[i] * base;
}
}
return ans;
}
void cpy()
{
for(int i = 0; i<=len ;++i) save[i] = gap[i];
}
// gap[i] (1<=gap<=len)
// gap[i] = 0 -> not chose;
// gap[i] = 1 -> chose;
void output()
{
for(int i = 1;i<=len;++i){
printf("%d",num[i]);
if(i!=len){
if(save[i] == 1) printf(" ");
}
}
printf("\n");
}
void dfs(int depth,int tag)
{
if(len==depth){
int t = sum();
if(t>ret && t<=tar){
judge = true;
time = 1;
cpy();
ret = t;
}else if(t==ret && t<=tar){
time++;
}
return;
}
gap[depth] = tag;
if(depth == len-1){
dfs(depth+1,1);//chose gap
}else{
dfs(depth+1,1);//chose gap
dfs(depth+1,0);//don't chose gap
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%lld %s",&tar,nu) && tar){
init();
dfs(0,1);
if(judge){
if(time>1) printf("rejected\n");
else {printf("%d ",ret);output();}
}else printf("error\n");
}
return 0;
}
比较难设计的就是sum函数。
int sum()
{
int base = 1; int ans = 0; int t = 0;
for(int i = len ; i>=0; --i){
if(gap[i] == 0){
base*=10;
t += num[i] * base;
}else if(gap[i] == 1){
ans+=t; base = 1;
t = num[i] * base;
}
}
return ans;
}
sum 函数是从右向左计算的,大致思路为:
1.如果gap[i] = 1, 则将t累加到ans上,重置base和t;
2.如果gap[i] = 0,则base自乘10,然后和num[i]相乘累加到t上。
自由向左,相当于模拟的从低位到高位,算起来比较自然。cpy函数完成对最优解的拷贝。inti函数预处理,将读入的字符串转换成数字。然后就是注意对多解的判断。
桃李春风一杯酒,江湖夜雨十年灯。