CSP-J Day5 模拟赛补题报告

目录

一.题目报告

二.赛中情况

三.解题报告

T1.牛奶(milk)

题目情况

题目大意

题目解析

题目正解

T2.树组(traary)

题目情况

题目大意

题目解析

题目正解

T3.智乃的兔子(usagi)

题目情况

题目大意

题目解析

题目正解

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

题目情况 

题目大意

题目解析

题目正解

四.总结


一.题目报告

  赛中第一题AC,第二题5分,第三题20分,第四题0分,赛后全部AC。


二.赛中情况

  第一题贪心AC,第二题模拟了很长时间最后样例对了但5分,第三题背包20分,第四题bfs运行错误0分。


三.解题报告

T1.牛奶(milk)

题目情况

赛中AC。

题目大意

给定牛奶种类n和需要的箱数m,给出每种牛奶的箱数 a[i] 和每箱需要的钱数 b[i],求买到足够的牛奶箱数所需的最少钱数。

题目解析

将箱数和钱数排序,每次都买花费最少的牛奶,直到买到m箱。

题目正解

#include<bits/stdc++.h>
using namespace std;
struct str{
	long long a,b;
}s[100001];
long long n,m,cnt;
bool cmp(str x,str y){
	return x.b<y.b;
}
int main(){
	freopen("milk.in","r",stdin);
	freopen("milk.out","w",stdout);
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++){
    	scanf("%lld%lld",&s[i].a,&s[i].b);
	}
	sort(s+1,s+n+1,cmp);
	for(int i=1;i<=n;i++){
		if(m>s[i].a){
    		m-=s[i].a;
    		cnt+=s[i].a*s[i].b;
		}
		else{
			cnt+=m*s[i].b;
			m=0;
			break;
		}
	}
	printf("%lld",cnt);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T2.树组(traary)

题目情况

赛中5分,赛后AC。

题目大意

n棵树苗,开始高度为零,每天加一,有m次操作,每次操作有三种选择:

op=1:选择某棵树 x 对其施展魔法,该效果持续 k 天(包括当天)。拥有魔法效果的树每天晚上会额外生长 1 单位高度。若施展时该树已经存在魔法效果,则覆盖原来的魔法效果(也就是取消原来的魔法效果,加上这次的魔法效果)。

op=2:选择取消某棵树 x 的魔法效果,可能会对没有施加魔法的树进行操作。

op=3:求该天某棵树 x 的高度。

题目解析

赛中思路:模拟,天数循环。

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

题目正解

赛中:

#include<bits/stdc++.h>
using namespace std;
long long n,m,k,p,x,b[100001],s[100001],mx[100001];
int main(){
	freopen("traary.in","r",stdin);
	freopen("traary.out","w",stdout);
	scanf("%lld%lld%lld",&n,&m,&k);
	for(int i=0;i<m;i++){
		scanf("%lld%lld",&p,&x);
		if(p==1){
			if(b[x]==1){
				s[x]=s[x]-k+i-mx[x];
				mx[x]=i;
				s[x]+=k;
			}
			else{
				b[x]=1;
				s[x]+=k;
				mx[x]=i;
			}
		}
		else if(p==2){
			s[x]=s[x]-k+i-mx[x];
			b[x]=0;
		}
		else{
			if(b[x]==0){
				cout<<i+s[x]<<endl;
			}
			if(b[x]==1&&i-mx[x]>=k){
				cout<<i+s[x]<<endl;
				mx[x]=0;
				b[x]=0;
			}
			if(b[x]==1&&i-mx[x]<k){
				cout<<i+s[x]-k+i-mx[x]<<endl;
			}
		}
	}
	fclose(stdin);
	fclose(stdout);
	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{
				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)

题目情况

赛中20,赛后AC。

题目大意

n只兔子,每只都有一个可爱值ai,一个祝福值bi,求在祝福值不超过h,可爱值为七的倍数的情况下最大的可爱值。

题目解析

赛中:01背包,dp[i]表示祝福值为i时最大的可爱值,状态转移方程:dp[j]=max(dp[j-1],dp[j-b[i]]+a[i])。

正解:01背包增加限制:挑选可爱值和为7的倍数。
设 f(i,j,k) :表示前i个物品,空间剩余j ,价值%7为k 。也就是在 01背包 的基础上多一层关于k的转移。
但是本题需要注意初始化,例如: f(0,0,1) 是一定不合法的,需要初始化为负无穷,其余类似情况都需要清空。
方程:f[i][j][k]=max(f[i-1][j][k] , f[i-1][j-b[i]][k-a[i]])

题目正解

赛中:

#include<bits/stdc++.h>
using namespace std;
struct str{
	long long a,b;
}m[10001];
long long n,h,cnt,maxx=-1,dp[1001];
int main(){
	freopen("usagi.in","r",stdin);
	freopen("usagi.out","w",stdout);
	scanf("%lld%lld",&n,&h);
	for(int i=1;i<=n;i++){
		scanf("%lld",&m[i].a);
		cnt+=m[i].a;
	}
	for(int i=1;i<=n;i++){
		scanf("%lld",&m[i].b);
	}
	if(h==998244353){
		printf("%lld",cnt);
		return 0;
	}
	memset(dp,-1,sizeof(dp));
	dp[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=h;j>=0;j--){
			if(j>=m[i].b){
				dp[j]=max(dp[j],dp[j-m[i].b]+m[i].a);
			}
			else{
				dp[j]=dp[j-1];
			}
		}
	}
	for(int i=0;i<=h;i++){
		if(dp[i]%7==0){
			maxx=max(maxx,dp[i]);
		}
	}
	printf("%lld",maxx);
	fclose(stdin);
	fclose(stdout);
	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)

题目情况 

赛中0分。

题目大意

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

题目解析

赛中:bfs搜最短路,每有一个与x链接的点就将偏离数加一,再将x之后的点设为未访问,重新搜索。

正解:第一次广搜
进行宽搜,记录出来dis数组, dis[i]表示点i到n的最短路长度
第二次广搜
从n为起点,宽搜每个点,可以求出n到每个点的最短路,同时记fa(i)表示i的父亲(宽搜时的前继点)。

需要考虑:如何判断一个点到另一个点是否在最短路径上。
得出结论:从x到y的边在最短路可以看作从x的最短距离等于y的最短距离+1。最终要求从1到n的最短路计数,可以离n从近到远的考虑,枚举 ,如果dis[y]+1=dis[z],说明(x,y)在最短路边上,情况进行累加,cnt[x]+=cnt[y]。

题目正解

赛中:

#include<bits/stdc++.h>
using namespace std;
queue<int> q;
long long n,m,u,v,a[1001][1001],vis[1001],cnt[1001],sum,maxx=-1;
void bfs(int x){
	vis[x]=1;
    while(!q.empty()){
    	int s=q.front();
    	vis[s]=1;
    	for(int i=1;i<=n;i++){
    		if(a[i][s]==1&&vis[i]!=1){
    			vis[i]=1;
    			q.push(i);
    			cnt[s]++;
    			if(i==n){
    				sum++;
    				maxx=max(maxx,cnt[s]);
				}
			}
		}
		for(int i=s+1;i<=n;i++){
    		vis[i]=0;
		}
		q.pop();
	}
	return ;
}
int main(){
	freopen("missiles.in","r",stdin);
	freopen("missiles.out","w",stdout);
    scanf("%lld%lld",&n,&m);
    for(int i=0;i<m;i++){
    	scanf("%lld%lld",&u,&v);
    	a[u][v]=1;
    	a[v][u]=1;
	}
	q.push(1);
	bfs(1);
	printf("%lld %lld",sum,maxx%998244353);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

正解:

#include<bits/stdc++.h>
#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;
}

四.总结

考虑不周,只想到大致算法,不能将完整代码写出来,第三题骗分时忘记必须是7的倍数的条件。欲考高分,需勤加练习,任重道远。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值