目录
P2036 [COCI2008-2009#2] PERKET
小结:
1、过了一道用01背包做的题目(没想到,最后一次错误竟然是数组开小了)
2、又一次应用了深搜dfs,(说实话实在没看出来,是从题解得出的灵感,初学还是不行呀,必须多找写题目练练手,找找灵感)
3、回溯算法
4、积累了一些小知识
P2392 kkksc03考前临时抱佛脚
题目背景
kkksc03 的大学生活非常的颓废,平时根本不学习。但是,临近期末考试,他必须要开始抱佛脚,以求不挂科。
题目描述
这次期末考试,kkksc03 需要考 44 科。因此要开始刷习题集,每科都有一个习题集,分别有 s_1,s_2,s_3,s_4s1,s2,s3,s4 道题目,完成每道题目需要一些时间,可能不等(A_1,A_2,\ldots,A_{s_1}A1,A2,…,As1,B_1,B_2,\ldots,B_{s_2}B1,B2,…,Bs2,C_1,C_2,\ldots,C_{s_3}C1,C2,…,Cs3,D_1,D_2,\ldots,D_{s_4}D1,D2,…,Ds4)。
kkksc03 有一个能力,他的左右两个大脑可以同时计算 22 道不同的题目,但是仅限于同一科。因此,kkksc03 必须一科一科的复习。
由于 kkksc03 还急着去处理洛谷的 bug,因此他希望尽快把事情做完,所以他希望知道能够完成复习的最短时间。
输入格式
本题包含 55 行数据:第 11 行,为四个正整数 s_1,s_2,s_3,s_4s1,s2,s3,s4。
第 22 行,为 A_1,A_2,\ldots,A_{s_1}A1,A2,…,As1 共 s_1s1 个数,表示第一科习题集每道题目所消耗的时间。
第 33 行,为 B_1,B_2,\ldots,B_{s_2}B1,B2,…,Bs2 共 s_2s2 个数。
第 44 行,为 C_1,C_2,\ldots,C_{s_3}C1,C2,…,Cs3 共 s_3s3 个数。
第 55 行,为 D_1,D_2,\ldots,D_{s_4}D1,D2,…,Ds4 共 s_4s4 个数,意思均同上。
输出格式
输出一行,为复习完毕最短时间。
输入输出样例
输入 #1复制
1 2 1 3 5 4 3 6 2 4 3输出 #1复制
20
first:
想的太简单了(很,,,,):(这个可不对啊!!)
先把一个科目只有一个题目的加起来
if(s2==1) sum+=b[0];
其他的科目直接把时间总和除以了2,然后分了两种情况
if(temp%2==0) sum+=(temp/2); else sum+=((temp/2)+1);
正解:
1、典型的01背包问题,但由于他是个双核
2、先要知道将一边的用脑时间加到最接近一半则另一边用脑时间就是正确思路
3、利用dp求出用脑时间加到最接近一半的用脑时间
4、此科目总时间减去用脑时间加到最接近一半的用脑时间
5、最后的总时间是4个科目的最优时间解之和
6、每次使用dp要记得清零
上代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
int max(int a,int b)
{
if(a>=b)
return a;
else
return b;
}
int dp[100][10000];
int sum=0,s[100],a[100],time[100],p[100];
int main()
{
for(int i=1;i<=4;i++)
scanf("%d",&s[i]);//四个背包
for(int k=1;k<=4;k++)//依次进行枚举
{
for(int r=1;r<=s[k];r++)
{
scanf("%d",&a[r]);
time[k]+=a[r];//记录背包大小
}
memset(dp,0,sizeof(dp));//清空背包
for(int i=1; i<=s[k]; i++) //题目
{
for(int j=1; j<=time[k]/2; j++) //总时间
{
if(j<a[i])//不能装下当前物品
dp[i][j]=dp[i-1][j];
else//能装下当前物品,选择最优解(即时间更接近time[k]的情况)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+a[i]);
}
}
p[k]=time[k]-dp[s[k]][time[k]/2];
}
for(int i=1; i<=4; i++)
sum+=p[i];
printf("%d",sum);
return 0;
}
P2036 [COCI2008-2009#2] PERKET
题目描述
Perket 是一种流行的美食。为了做好 Perket,厨师必须谨慎选择食材,以在保持传统风味的同时尽可能获得最全面的味道。你有 nn 种可支配的配料。对于每一种配料,我们知道它们各自的酸度 ss 和苦度 bb。当我们添加配料时,总的酸度为每一种配料的酸度总乘积;总的苦度为每一种配料的苦度的总和。
众所周知,美食应该做到口感适中,所以我们希望选取配料,以使得酸度和苦度的绝对差最小。
另外,我们必须添加至少一种配料,因为没有任何食物以水为配料的。
输入格式
第一行一个整数 nn,表示可供选用的食材种类数。
接下来 nn 行,每行 22 个整数 s_isi 和 b_ibi,表示第 ii 种食材的酸度和苦度。
输出格式
一行一个整数,表示可能的总酸度和总苦度的最小绝对差。
输入输出样例
输入 #1复制
1 3 10输出 #1复制
7输入 #2复制
2 3 8 5 8输出 #2复制
1输入 #3复制
4 1 7 2 6 3 8 4 9输出 #3复制
1说明/提示
数据规模与约定
对于 100\%100% 的数据,有 1 \leq n \leq 101≤n≤10,且将所有可用食材全部使用产生的总酸度和总苦度小于 1 \times 10^91×109,酸度和苦度不同时为 11 和 00。
说明
- 本题满分 7070 分。
- 题目译自 COCI2008-2009 CONTEST #2 PERKET,译者 @mnesia。
****学好递归真的很重要啊,抽空复习一下吧!!
思路
case1:选择所有的配料,酸度相乘,苦度相加,min记录差值
case2:只选择一种配料,将每种的差值与上一次min比较,取最小
case3:选2~n种配料(应用dfs)
1、vis数组记录用没用过(记得回溯)
2、把选2~n种配料的每次差值都算出来,算一次比较一次,用min记录最小值
代码实现:
#include<stdio.h>
#include<string.h>
#include<math.h>
int n,minn,x=1,y;
int a[100],b[100];
int vis[100];//标记
int min(int t,int k)
{
if(t<k)
return t;
else
return k;
}
void dfs(int c){
if(c==0){
minn=min(minn,abs(x-y));
return;
}
for(int i=1;i<=n;i++){
if(!vis[i]){
vis[i]=1; x*=a[i];y+=b[i];c--;
dfs(c); //继续搜索
c++;x/=a[i];y-=b[i];
vis[i]=0;//回溯
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i],&b[i]);
for(int i=1;i<=n;i++)//选择所有配料
{
x*=a[i];
y+=b[i];
}
minn=abs(x-y);
for(int i=1;i<=n;i++)//只选择一种配料的情况
minn=min(minn,abs(a[i]-b[i]));
for(int i=2;i<n;i++) //选2~n种配料的情况
{
memset(vis,0,sizeof(vis));
x=1;
y=0;
dfs(i);
}
printf("%d",minn);
return 0;
}
P2404 自然数的拆分问题
题目描述
任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。现在给你一个自然数n,要求你求出n的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。然后你需要输出这些序列,其中字典序小的序列需要优先输出。
输入格式
输入:待拆分的自然数n。
输出格式
输出:若干数的加法式子。
输入输出样例
输入 #1复制
7输出 #1复制
1+1+1+1+1+1+1 1+1+1+1+1+2 1+1+1+1+3 1+1+1+2+2 1+1+1+4 1+1+2+3 1+1+5 1+2+2+2 1+2+4 1+3+3 1+6 2+2+3 2+5 3+4说明/提示
用回溯做。。。。
n\le 8n≤8
思路
简单的回溯算法应用,可以看接下来的回溯算法模板
代码实现
#include<stdio.h>
int a[1001],n;
void dfs(int x,int y)//x代表第几个数,y代表数字还剩多少
{
if(y==0&&x>2)
{
for(int i=1;i<x-1;i++)
printf("%d+",a[i]);
printf("%d\n",a[x-1]);
return;
}
for(int i=1;i<=y;i++)
{
if(i>=a[x-1])//后面数字大于前面数字
{
a[x]=i;y=y-i;
dfs(x+1,y);
a[x]=0;y=y+i;
}
}
}
int main()
{
scanf("%d",&n);
dfs(1,n);
return 0;
}
回溯算法
模板
void dfs(int x)
{
if(终止条件)
{
输出解;
return;
}
for(int i=1;i<=方案数;i++)
{
if(方案可行)
{
保存路径;
dfs(x+1);
回溯;
}
}
}
小知识
1、memset函数
头文件:#include<string.h>
用法:memset(a,0,sizeof(a))
作用:将数组a初始化
2、ios::sync_with_stdio(false);(c++中会用到的)
有时候,使用普通的cin和cout会超时,所以我们每次只能打scanf和printf,然后一堆的占位符巨麻烦),为什么cin和cout比scanf和printf用的时间多? 这是因为C++中,cin和cout要与stdio同步,中间会有一个缓冲,所以导致cin,cout语句输入输出缓慢,这时就可以用这个语句,取消cin,cout与stdio的同步,说白了就是提速,效率基本与scanf和printf一致。