CSP-J第五场模拟赛补题报告2024.10.5

 一、得分情况

T1 100pts , T2 30pts , T3 20pts , T4 0pts ,总分150pts。赛后AC。

二、比赛概况

20min时过了第一题,想着做对前两道,砸了2h50min去想T2正解,没做出来。最后5min写了T2T3的暴力就结束了。

三、解题报告

T1 牛奶(milk)

得分情况

比赛时AC。

题目大意 

有 n 家牛奶供应商 ,第 i 家有 a_{i}盒牛奶,每盒b_{i}元。

出题人想要强健(?,他每月要喝 m 盒牛奶,问从供应商处购买 m 盒牛奶的最小价钱。

赛时思路

看到排序想到了结构体排序,利用贪心思想,尽量选择单价较小的奶。

正解思路

结构体排序即可

赛时代码

#include<bits/stdc++.h>
using namespace std;
struct node{
	long long num,pri;
}a[114514];
long long n,m,ans,milk;
bool cmp(node x,node y){
	if(x.pri!=y.pri)return x.pri<y.pri;//结构体排序
	else return x.num>y.num;
}
int main(){
	scanf("%lld %lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld %lld",&a[i].num,&a[i].pri);
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n&&milk<m;i++){
		if(m-milk>=a[i].num){
			milk+=a[i].num;
			ans+=a[i].num*a[i].pri;
		}
		else{
			ans+=(m-milk)*a[i].pri;
			milk=m;
		}
	}
	cout<<ans;
	return 0;
}

正解代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct node {
	int a,b;
	inline bool operator < (const node& o)const {
		return b<o.b;
	}
} datas[maxn];
int n,m;
int main() {
	cin>>n>>m;
	for(int i=1; i<=n; i++) {
		cin>>datas[i].a>>datas[i].b;
	}
	sort(datas+1,datas+1+n);
	long long ans=0;
	for(int i=1; i<=n&&m>0; i++) {
		int t=min(datas[i].a,m);
		m-=t;
		ans+=1ll*t*datas[i].b;
	}
	cout<<ans<<endl;
	return 0;
}

T2 树组(strray)

得分情况

比赛时40pts。
正解差一点,打的暴力。

题目大意

有n棵树栽在一条直线内,每棵树每天成长1高度,有三种操作

第一种是对一棵树 x 施加魔法,施了魔法的树每天额外成长1高度

第二种是取消某棵树的魔法,第三种是询问树 x 的高度

赛时思路

m天内做暴力,循环给树加高度,若当前有魔法就额外+1.

正解思路

p_{i}记录第i棵树最近一次施法时间。如果当前要对 i 施加魔法,那么上一次施法时间就是 p_i ,当前时间是枚举到 t 的时间 ,方便判断 t 和 p_i之差是否已经超过 k,超过 k 表示上次施法效果已经没有了,不超过可以继续更新施法时间,更新w_i 。
w_i:记录p_i 之前 i 的魔法加成

赛时代码

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int a[114514],b[114514];
int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=m;i++){
		int op,x;
		cin>>op>>x;
		if(op==1){
			b[x]=k;
		}
		if(op==2){
			b[x]=0;
		}
		if(op==3){
			cout<<a[x]<<endl;
		}
		for(int j=1;j<=n;j++){
			a[j]++;
			if(b[j]>0){
				b[j]--;
				a[j]++;
			}
		}
	}
	return 0;
}

正解代码

#include <set>
#include <ctime>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define MAXN 110000
#define vint vector<int>
using namespace std;
int n,m,k;
int a[MAXN];
vector<pair<int,int> > vec[MAXN];
int ans[MAXN];
int main() {
	memset(ans,-1,sizeof(ans));
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1; i<=m; i++) {
		int op,x,y;
		scanf("%d%d",&op,&x);
		vec[x].push_back({op,i});
	}
	for(int i=1; i<=n; i++) {
		int p=-1;
		int add=0;
		for(int j=0; j<vec[i].size(); j++) {
			int x = vec[i][j].first , y = vec[i][j].second;
			if(x==1) {
				if(p>0)add+=min(y-p,k);
				p=y;}
				else if(x==2) {
					if(p>0)add+=min(y-p,k);//魔法消失之前,记录增量高度。
					p=-1;
				} else {//第y棵树的高度就是y-1
					ans[y] = y-1+add+(p>0?min(y-p,k):0);
				}
			}
		}
		for(int i=1; i<=m; i++) {
			if(ans[i]>=0)printf("%d\n",ans[i]);
		}
		return 0;
	}

 T3 智乃的兔子(usagi)

得分情况

赛时20pts。

题目大意

01背包,但是价值总和要是7的倍数

赛时思路

二进制模拟01背包,直接判断条件是否成立。

解题思路

在dp二维数组前 i 个物品,空间剩余 j 的情况下再设置一维,即当前价值%7。

注意初始化。正解代码里顺序是正序,为了防止变成完全背包就再设一个数组。

方程:tmp[j][i]=max(f[j-1][i],f[j-1][((i-a[j])%7+7)%7]+a[j]);

赛时代码

#include<bits/stdc++.h>
using namespace std;
int a[11004],w[10005],c[10005];
long long ans,cnt;
int main(){
//	freopen("usagi.in","r",stdin);
//	freopen("usagi.out","w",stdout);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>c[i];
		
	}for(int i=1;i<=n;i++){
		cin>>w[i];
		
	}
	while(a[n+1]!=1){
		
		long long cnt=0,cnt1=0;
		a[1]++;
		for(int i=1;i<=n;i++){
			if(a[i]){
				cnt+=w[i];
				cnt1+=c[i];
			}
		}
		if(cnt<=m&&cnt1%7==0){
			ans=max(ans,cnt1);
		}
		cnt=0;
		while(a[++cnt]==2){
			a[cnt]=0;
			a[cnt+1]++;
		}
		
	}
	cout<<ans;
	return 0;
}

正解代码

#include<bits/stdc++.h>
#define LL long long
#define MAXN 11000
#define MAXH 1100
#define vint vector<int>
using namespace std;
int n,m,k;
int a[MAXN];
int b[MAXN];
LL dp[2][MAXH][7];
LL t[MAXN][7];
LL f[MAXH][7];
LL g[MAXH][7];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)scanf("%d",&b[i]);

    if(m==998244353){
        for(int i=1;i<7;i++)t[0][i]=-1e18;
        for(int i=1;i<=n;i++){
            for(int j=0;j<7;j++){
                t[i][j]=max(t[i-1][((j-a[i])%7+7)%7]+a[i],t[i-1][j]);
            }
        }
        cout<<t[n][0]<<endl;
        return 0;
    }
    
    for(int i=0;i<MAXH;i++)
        for(int j=0;j<7;j++)
            g[i][j]=-1e18;
    g[0][0]=0;
    
    for(int i=1;i<=n;i++){
        for(int w=0;w<=m;w++)
            for(int j=0;j<7;j++){
                f[w][j]=g[w][j];
                if(w>=b[i])f[w][j]=max(g[w-b[i]][((j-a[i])%7+7)%7]+a[i],f[w][j]);
            }
        swap(f,g);
    }
    LL ans=0;
    for(int i=0;i<=m;i++)
        ans=max(ans,g[i][0]);
    cout<<ans<<endl;
    return 0;
}

 T4 一颗成熟的奥术飞弹(missiles)

得分情况

比赛时0pts。

题目大意

n 个点的无向连通图,无重边和自环,目标是从 1 点到达 n 点。在一个点上,如果有多条最短路径到达终点,则 可能偏离数 加一,求出最短路径的总条数,以及所有最短路中可能偏离数的最大值。

解题思路

最短路计数问题。
第一次广搜
进行宽搜,记录出来 dis 树组,dis_i 表示点 i 到 n 的最短路长度
第二次广搜
从 n 为起点,宽搜每个点,可以求出 n 到每个点的最短路,同时记 fa(i) 表示i的父亲(宽搜时的前继点)。最终要求从 1 到 n 的最短路计数,可以离 n 从近到远的考虑,枚举 (x,y),

如果dis_y + 1 =dis_x,说明x,y在最短路边上,情况累加。

正解代码

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MAXN 110000
#define MAXH 1100
#define vint vector<int>
using namespace std;
vint son[MAXN];
int fa[MAXN], dis[MAXN], ans[MAXN], cnt[MAXN];
int n, m;
bool vis[MAXN];
queue<int> q;
vint vec;
void BFS() {  // 逆序记录。
    q.push(n);
    vis[n] = 1;
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        vec.push_back(x);  // 记录广搜序列
        for (int i = 0; i < son[x].size(); i++) {
            int y=son[x][i];//y is x de linjiedian
            if (!vis[son[x][i]]) {
                vis[son[x][i]] = 1;
                q.push(son[x][i]);
                fa[son[x][i]] = x;
                dis[son[x][i]] = dis[x] + 1;
            }
        }
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        son[x].push_back(y);
        son[y].push_back(x);
    }
    BFS();       // 先进xing一次广搜,记录fa树组、记录距离树组
    cnt[n] = 1;  // 记录最短飞行弹道条数
    for (int i = 0; i < vec.size(); i++) {  // 从近到远考虑每个点
        int flag = 0;
        for (int j = 0; j < son[vec[i]].size(); j++) {
            if (dis[vec[i]] == dis[son[vec[i]][j]] + 1) {  // 相邻两个点,距离也相差1,说明在最短路边上。
                ans[vec[i]] = max(ans[vec[i]], ans[son[vec[i]][j]]);
                cnt[vec[i]] += cnt[son[vec[i]][j]];  // 所有能到son[vec[i]][j]的点都能走到vec[i]去,因此累加到vec[i]中。
                cnt[vec[i]] %= 998244353;
                if (son[vec[i]][j] != fa[vec[i]])  // 记录时不能走回父亲点去
                    flag = 1;
            }
        }
        ans[vec[i]] += flag;
    }
    cout << cnt[1] << " " << ans[1] << endl;
    return 0;
}

四、总结

题目较难,需要在做不出正解的时候快速把保底分拿到,保证成绩。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值