7月23日测验小结
测验共4题,分别为:
-
小明的书包(bigbag)
-
邮票(post)
-
握手(shake)
-
海狸先生,排队!(ekg)
总体感觉:
动归是新学的,也是这次来石中的第一个挑战——
挺有意思的
有点难度,再加上内容有点多,背包大礼包,上升&&公共子序列…
这次测验的最后一题愣是没写出来,其他难度都还好,下面是题目及题解:
小明的书包(bigbag)
题意描述
小明有一个很大的书包,容量为c。但大家也知道,现在的课本也很重,以至于小明没有办法一次性带上所有的课本。小明把课本的重量和重要性告诉你,请你帮他算算选择哪些课本能使得重要性之和最大。
输入格式:bigbag.in
第一行有两个正整数n和c,代表课本的数量和书包的容量。
接下来n行,每行有2个正整数wi和vi,分别代表该课本的重量和重要性。
输出格式:bigbag.out
一行一个数,输出最大可能的重要性之和。
输入样例1:
5 12
10 4
2 1
9 5
4 2
5 4
输出样例1:
7
数据范围:
对于60%的数据,背包总容量和课本重量<=100
对于100%的数据,n <= 100,课本重要性<=100, 背包总容量和课本重量<=10^9
分析
很显然,这是个普通的01背包问题,
于是乎写出模板:
for(int i=1;i<=n;i++)
for(int j=W;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]]+v[i])
但是注意:
背包总容量和课本重量<=10^9
这就需要变变形了
还记得老师在课上提到过:
//当某一维存不下时,把某一维和结果交换一下
//f[i][j]表示前i个物品价值为j的最小体积
j从表示体积变成表示价值,f([i])[j]则表示前i个物品能取到j的最小体积。
还有一个问题,求得值后,怎么取出合法最大价值呢?
由题意得:最大价值<=所有书本重要性之和,所以先定义sum计算总和;
循环模板从sum递减到1:若f[j]<=c,输出j即为最大的合法价值。
AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,v[110],sum;
long long f[10010],w[110],c;
int main()
{
// freopen("bigbag.in","r",stdin);
// freopen("bigbag.out","w",stdout);
scanf("%d%lld",&n,&c);
for(int i=1;i<=n;i++)
scanf("%lld%d",&w[i],&v[i]),sum+=v[i];
for(int i=1;i<=sum;i++) f[i]=INT_MAX;
for(int i=1;i<=n;i++)
{
for(int j=sum;j>=v[i];j--)
{
f[j]=min(f[j],f[j-v[i]]+w[i]);
}
}
for(int i=sum;i>=0;i--)
if(f[i]<=c)
{
printf("%d",i);
break;
}
return 0;
}
邮票(post)
题意描述
市面上总共有N张不同的邮票,编号0至N-1。第i张邮票的价格是p[i]。你的目标是能收集尽量多的不同邮票。如果你有足够多的钱,那显然不是问题,全部买回来就行了。但是你手头上一分钱也没钱,幸运的是一开始你手头上已经有m张邮票了,这些邮票的编号保存在数组b[i],0<=i<m。于是你决定通过卖掉你手头上的某些邮票,再买进一些其他的邮票,通过这样卖出买进,你最后最多可以有几张邮票(当然你也可以不做任何买卖)?注意:对于任意的0<=j<N, 如果你卖出邮票j,那么你可以得到p[j]金钱,如果你想买进邮票j,那么你得付p[j]金钱。
输入格式:post.in
第一行,一个整数N,表示有N张邮票。1<=N<=50。
第二行, N个整数,第i个整数表示p[i],1<=p[i]<=1000000。
第三行,一个整数m,表示你现在已经有了m张邮票。0<=m<=n.
第四行, m个整数,第i个整数表示b[i]。
输出格式:post.out
一个整数,通过买卖,你最多可以有几张邮票?
输入样例
5
4 13 9 1 5
3
1 3 2
输出样例
4
数据范围
1<=N<=50。
1<=p[i]<=1000000。
0<=m<=n。
m个整数,第i个整数表示b[i]。
分析
这道题完全就是01背包模板题啊
NoNoNo这是个贪心(01背包貌似会超时。。。)
这道题关键在于如何用现有的金币换取最多的邮票。
现有的。。。何不如卖掉所有的邮票,再买低价的呢?
接下来就是贪心了(sort排序由小到大判断),但我貌似还并不是很懂贪心。。。那就直接上代码把:
#include<bits/stdc++.h>
using namespace std;
int n,ans,v,p[50000010],m,b[60],f[1000010];
int main()
{
// freopen("post.in","r",stdin);
// freopen("post.out","w",stdout);
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&p[i]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d",&b[i]);
v+=p[b[i]];
}
f[0]=0;
sort(p,p+n);
for(int i=0;i<n;i++)
if(v>=p[i])
{
v-=p[i];
ans++;
}
cout<<ans<<endl;
return 0;
}
握手(shake)
题意描述
有N个人(N是偶数,不超过50),坐在一个园桌旁开会,会后,他们决定握手,每个人必须且只能挑另一个人握手, 假设对于握手的两个人,我们画都一条直线,我们要求所有的直线不能有交点。有多少种不同握手方法?
输入格式:shake.in
一个整数N。N是偶数,不超过50。
输出格式:shake.out
一个整数,不同的合法握手方案。
输入样例1:
2
输出样例2:
1 //(只有2个人,所以他们握手肯定是合法的。)
输入样例2:
4
输出样例2:
2
数据范围:
N<=50
分析
乍看这道题,不就是n/2吗?
几秒后冷静下来:不是啊,这到了6不就崩了吗。。。
没错,这没有那么简单~
这里其实是有递推关系的;
假设L=一顶点向任意顶点连接所得的满足条件的直线;
f[i]=f[L左边]+f[L右边] (L左边+L右边+2=i)
例如:f[6]可分成3种:
- f[0]*f[4] (乘法原理);
- f[2]*f[2];
- f[4]*f[0];
种数相加结果为:
f [ 6 ] = f [ 0 ] ∗ f [ 4 ] + f [ 2 ] ∗ f [ 2 ] + f [ 4 ] ∗ f [ 0 ] = 2 + 1 + 2 = 5 f[6]=f[0]*f[4]+f[2]*f[2]+f[4]*f[0]=2+1+2=5 f[6]=f[0]∗f[4]+f[2]∗f[2]+f[4]∗f[0]=2+1+2=5
多列几条,不难发现这个递推关系式:
f [ i ] = f [ 0 ] ∗ f [ i − 2 ] + f [ 2 ] ∗ f [ i − 4 ] + … … + f [ i − 2 ] ∗ f [ 0 ] ; f[i]=f[0]*f[i-2]+f[2]*f[i-4]+……+f[i-2]*f[0]; f[i]=f[0]∗f[i−2]+f[2]∗f[i−4]+……+f[i−2]∗f[0];
从0开始,每次加2得出数列:
1,1,2,5,14……
延伸:此数列是卡特兰数的一部分
基本的核心代码算是搞定了。。。
别忘记循环里要列举L直线的另一个端点!!(具体列举方法见代码)
AC代码:
#include<bits/stdc++.h>
using namespace std;
long long n,f[60];
int main()
{
//freopen("shake.in","r",stdin);
//freopen("shake.out","w",stdout);
cin>>n;
f[0]=1;
for(int i=2;i<=n;i+=2)
{
for(int l=0;l<=i-2;l+=2)//列举满足连直线L的端点
{
f[i]+=f[l]*f[i-2-l];
}
}
cout<<f[n];
return 0;
}
海狸先生,排队!(ekg)
题意描述
聪明的海狸先生病了,所以他得去看医生。医生叫海狸先生去照心电图,但是心电图室门前排起了好长的队。海狸先生只好跟着排了队。
站了3小时后,聪明的海狸先生发现很多人并不清楚谁应该站在他们自己前面,这可导致队伍变得一团糟。于是海狸先生走到每个人面前,问他在队伍中他前面应该是谁。当然,有人是不知道的,那他可能下一个就能进心电图室,也可能还要等很长很长的时间……
正如你猜到的那样,海狸先生想叫你根据他问问题的结果确定他自己在队伍中的所有可能的位置。
输入格式:shake.in
第一行有两个正整数n和x(1<= x<= n),代表队伍的总人数和海狸先生的编号。海狸先生用1~n编号所有队伍中的人。
下一行有n个正整数a1, a2,…, an(0 <=ai<=n),ai表示第i个人前面的人的编号,ai = 0表示不知道。数据保证没有环,且任何一个人最多只会被一个人跟在后面。
输出格式:shake.out
按照从小到大的顺序输出海狸先生所有可能的位置。
输入样例1:
6 1
2 0 4 0 6 0
输出样例1:
2
4
6
数据范围:
对于30%的数据,最多有20个人不知道自己前面是谁。
对于100%的数据,n <= 1000
分析
这道令我百思不得其解的题,总共有这么几个核心思想及其实现过程:
-
寻找以0为分隔线的数链,并将其长度存入一数组(注意,含海狸先生的要特别标明)
-
设f[i][j]表示前i个数能否加和为j(bool)
有关系式:
i f ( f [ i ] [ j ] = = f [ i − 1 ] [ j ] ∣ ∣ f [ i ] [ j ] = = f [ i − 1 ] [ j − a [ i ] ] ) f [ i ] [ j ] = 1 ; if(f[i][j]==f[i-1][j]||f[i][j]==f[i-1][j-a[i]]) f[i][j]=1; if(f[i][j]==f[i−1][j]∣∣f[i][j]==f[i−1][j−a[i]])f[i][j]=1;(可以省略 i i i)
- 将求得的在海狸先生所在数组前的不同的长度加上海狸先生在其组的位置,就是它可能出现的地方。
详细解释见代码
AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,x,sum,xc=-1,cur=0,m[1010],a[1010],c[1010];
/*
sum:不同链的长度;
xc:标注海狸先生在其链的位置;
cur:链的总数(忽略海狸先生所在的链);
m[1010]:记录每个人后面的编号(不知为0);
c[1010]:c[i]即第i个链的长度;
*/
bool f[1010],xcy;
//f[1010]:f[i]表示海狸先生所在链前能否达到长度i,能则1;
//xcy:表示是否找到海狸先生及其所在链;
void dfs(int k)
{
sum++;
if(k==x)//别忘了特判海狸先生所在的链
{
xc=sum;
xcy=1;//改变bool值
return;
}
if(m[k]) dfs(m[k]);
return;
}
int main()
{
scanf("%d%d",&n,&x);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
m[a[i]]=i;
}
for(int i=1;i<=n;i++)
{
if(a[i]==0)//当这个人前面的人不知道时,我们开始计算新的一个链
{
sum=0,xcy=false;
dfs(i);
if(xcy==0) c[++cur]=sum;//即忽略海狸先生所在的链
}
}
f[0]=1;
for(int i=1;i<=cur;i++)
for(int j=n;j>=c[i];j--)
if(f[j]==1||f[j-c[i]]==1)//进行01背包求在海狸先生所在链前面的长度的方案数
f[j]=1;
for(int i=0;i<=n;i++)
if(f[i]) cout<<i+xc<<endl;//最后加上海狸先生在其链的序号才是最终的答案
return 0;
}
2020.7.23 2020.7.23 2020.7.23