日期 2023年10月2日星期一
学号 S11142
一、比赛概况
总分400分,拿到30分,其中第1题30分,2题0分,3题0分,4题0分.
赛后补题:全部正确。
二、比赛过程
第一题没有彻底弄清题目意思。于是写了个不大对的代码就去做了第二题。第二题写了一半,感觉自己的方法太麻烦,就去做了第三题。第三题也没思路,就去做第四题。也没思路。于是又看二三题。最后第二题弄出来一个较麻烦的枚举。第三题用了个二分。第四题直接dp。第二题样例过了,第三题样例过了两个,第四题样例过了两个。本以为能拿点分,没想到后三个题一分没有。
三、解题报告
1.人员借调
(1)题目大意
小可从A地去B地帮忙,起初小可在A地,在AB之间往返一次需要花费400分钟。给出n件需要在B地处理的事情。问小可处理完这些事情至少需要多少分钟。
其中,小可如果在B地连续工作240分钟及以上,他将额外花费10080分钟作为惩罚。为了避免惩罚,他可以选择在连续工作快240分钟时返回A地,再回到B地继续工作。
(2)比赛中的思考
想到模拟,但是样例总是不对,也没有理解好题目意思。只拿到30分。
(3)解题思路
一共有两种方案:
1.小可一直在B地工作,并接受一次10080分钟的惩罚。那么他花费的时间将为去B地的400分钟+惩罚的10080分钟+所有事情花费时间的总和。
2.小可为了避免惩罚,在B地工作接近240分钟时返回A地,再回到B地继续工作。
其中,如果某件事情所花费的时间大于等于240分钟,意味着小可避免不了惩罚,那么他选择第一种方案肯定是最合理的。
否则,我们需要在两种方案中选出花费时间较小的一个。
(4)AC代码
#include<iostream>
using namespace std;
int n;
long long a[1005];
long long maxx,sum1=400+10080;
long long sum2=400;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
sum1+=a[i];//计算所有事情花费时间的总和
maxx=max(maxx,a[i]);//记录所有事情花费时间的最大值
}
//计算第二种方案
long long t=0;
for(int i=1;i<=n;i++){
if(a[i]+t>=240){//如果办完会超时
sum2+=400+a[i];//加往返一次的时间和办事花费的时间
t=a[i];//重新计时
}
else{
t+=a[i];
sum2+=a[i];//只加办事花费的时间
}
}
if(maxx>=240){//说明选择第一种方案更好
cout<<sum1<<endl;
}
else{//两种方案作比较
cout<<min(sum1,sum2)<<endl;
}
return 0;
}
2.计算
(1)题目大意
给定一个范围,找到最小的满足以下要求的数字:
所有数位上的数字之和为k,所有数位上的数字之积最大。
数据保证有解。
(2)比赛中的思考
尝试使用深搜凑和。觉得用字符串形式比较简单,结果代码出问题导致0分。
(3)解题思路
如果直接暴力枚举,只能得一半的分数(时间不够)。
所以我们可以空间换时间,先进行打表,再根据询问求解。
把1到5000000间每个数的数字之和,数字之积求出来,再根据给出的范围找答案。
(4)AC代码
#include<iostream>
using namespace std;
int t;
int m,n,k;
int maxx,maxnum;
int sum[5000005];
int num[5000005];
void work(){//打表
int tmp;
for(int i=1;i<=5000000;i++){
t=i;
num[i]=1;
while(t){
num[i]*=t%10;//数位之积
sum[i]+=t%10;//数位之和
t/=10;
}
}
}
int main(){
work();
cin>>t;
while(t--){
cin>>m>>n>>k;
maxx=-1;//maxx要赋负值,否则会漏掉数位之积为0的情况
for(int i=m;i<=n;i++){//遍历范围内每一个数字
if(sum[i]==k){//如果数位之和等于k
if(num[i]>maxx){//判断数位之积是否更大
maxx=num[i];
maxnum=i;
}
}
}
cout<<maxnum<<" "<<maxx<<endl;
}
return 0;
}
3.智能公交
(1)题目大意
数轴上有若干个点。给定若干数对。
求一个点,使得这个点到每一对点中的每一个点的距离与每对点两两间的距离之和最小。
如果有多个满足要求的点,输出最小的一个。
(2)比赛中的思考
尝试用二分。样例有一个不过。
(3)解题思路
每对点之间的距离都是固定的,所以我们只要保证要选的点和每个点的距离之和最小即可。
我们可以借助中位数的思想,算出最中间的点,即为答案。
(4)AC代码
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m;
int a[500005],b[500005];
int c[1000005];
long long ans;
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i];
ans+=abs(a[i]-b[i]);//计算每对点的距离之和
c[i*2-1]=a[i],c[i*2]=b[i];//把所有点存放到一起
}
sort(c+1,c+2*m+1);//排序,找到中间点
for(int i=1;i<=2*m;i++){
ans+=abs(c[m]-c[i]);//计算最短距离
}
cout<<c[m]<<" "<<ans<<endl;
return 0;
}
4.异或和
(1)题目大意
给定多个集合,在每个集合中选择若干个数,使得从每个集合中选出的若干个数的异或和之和最大。最多选择m个数字。
如果在一个集合中只选择了一个数字,那么它的异或之和为它本身。
(2)比赛中的思考
最后思考出dp方法,样例过了两个,最后因为数组开太大运行错误,0分。
(3)解题思路
先分别算出每一组在操作次数为j次时所能获得的最大异或和,再转换成分组背包问题进行解决。
(4)10分代码(只考虑m=n,组数为1时)
#include<iostream>
#include<cstring>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int n,m,a[2005];
int dp[2005][2050];
int main(){
cin>>n>>m;
int x;
for(int i=1;i<=n;i++){
cin>>a[i]>>x;
dp[1][a[i]]=1;
}
for(int i=2;i<=n;i++){
for(int j=1;j<=2047;j++){
if(dp[i-1][j]){//如果前i-1个数能够构造j
dp[i][j]=1;
dp[i][j^a[i]]=1;//当前能构成的数字异或当前数字一定可以构成
}
}
}
for(int i=2047;i>=1;i--){//找到最大值
if(dp[n][i]){
cout<<i;
break;
}
}
return 0;
}
(5)10分代码优化(m可以为任意时)
#include<iostream>
#include<cstring>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int n,m,maxx,a[2005];
int dp[2005][2050];
//dp[i][j]:前i个数能构成j最小的操作次数
int main(){
cin>>n>>m;
memset(dp,0x3f,sizeof(dp));
int x;
for(int i=1;i<=n;i++){
cin>>a[i]>>x;
dp[i][a[i]]=1;
}
for(int i=2;i<=n;i++){
for(int j=1;j<=2047;j++){
if(dp[i-1][j]!=INF){//如果前i-1个数能够构造j
dp[i][j]=min(dp[i][j],dp[i-1][j]);
dp[i][j^a[i]]=min(dp[i-1][j]+1,dp[i][j^a[i]]);
}
}
}
for(int i=1;i<=10;i++){
if(dp[n][i]<=m){
maxx=max(i,maxx);
}
}
cout<<maxx;
return 0;
}
(6)AC代码
#include<iostream>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;
int n,m;
int x,y;
int cnt[2005],a[2005][2005];
int dp[2005][2048];
int num[2005][2048];
int dp2[2005];
int main(){
cin>>n>>m;
//分组存放
for(int i=1;i<=n;i++){
cin>>x>>y;
cnt[y]++;
a[y][cnt[y]]=x;
}
//转换成分组背包
for(int zu=1;zu<=2000;zu++){//遍历每一组
if(cnt[zu]==0) continue;//没有这个组,跳过
memset(dp,0x3f,sizeof(dp));//赋初值
for(int i=1;i<=cnt[zu];i++){
dp[i][a[zu][i]]=1;///前i个数构成a[zu][i]一定只需要1个数本身即可
}
//处理dp数组
for(int i=2;i<=cnt[zu];i++){
for(int j=1;j<=2047;j++){
if(dp[i-1][j]!=INF){
dp[i][j]=min(dp[i][j],dp[i-1][j]);
dp[i][j^a[zu][i]]=min(dp[i][j^a[zu][i]],dp[i-1][j]+1);
}
}
}
//num[i][j]:第i组选j个数所能够构成的最大值
for(int j=1;j<=2047;j++){
int k=dp[cnt[zu]][j];构成j所需要的个数
if(k!=INF){
num[zu][k]=max(num[zu][k],j);
}
}
}
//分组背包
for(int i=1;i<=2000;i++){//枚举组数
for(int j=m;j>=1;j--){//枚举背包容量(操作次数)
for(int k=1;k<=cnt[i];k++){//枚举数据个数
if(j>=k){
dp2[j]=max(dp2[j],dp2[j-k]+num[i][k]);
}
}
}
}
cout<<dp2[m]<<endl;
return 0;
}
总结:
分数较低。继续努力。