CSP-J复赛模拟5——孙晨佑补题报告

一、题目分数

            T1【 牛奶 ( milk ) 】( 100 / 100 )

            T2【 树组 ( traary ) 】( 25 / 100 )

            T3【 智乃的兔子 ( usagi ) 】( 25 / 100 )

            T4【 一颗成熟的奥术飞弹 ( missiles ) 】( 0 / 100 )

总分:( 150 / 400 )

丸辣,考炸啦qwq,才15th

二、比赛过程

           T1 ( milk ) :

                     瞬杀,光速过,没啥好说的。

           T2 ( trarry ) :

                     一眼模拟,写了写发现模拟不出来,想了好一会,想出来个25pts的答案…

           T3 ( usagi ) :

                      一眼01背包,结果发现有5组数据里的背包总重量(对应的)为998244353,直接T掉了,无奈之下写了个特判,写了个暴搜,还是T了,遂只得25pts。

           T4 ( missiles ) :

                     一眼bfs,写着写着发现写不出来了,因为求错了,当时想着bfs加上dfs,但没敢写,还是爆零了。

三、题目思路与 AC code:

           T1 ( milk ) :

                      题目重现:

每天一杯奶,健壮切题人。

Meowowco 有每天喝牛奶的习惯,因为牛奶是膳食中蛋白质、钙、磷、维生素 A、维生素 D 和维生素 B2 的重要来源之一,可以让经常出勤的 Meowowco 变得健壮。因此她每个月都会去买一些牛奶屯在冰箱里。

今天又到了采购的日子,Meowowco 又来到了熟悉的超市,看着冰箱里陈列着价格不同的牛奶,她摸了摸自己的钱包,

“糟糕,出勤花了太多的钱了。。。。。。”

不过这都不是问题,毕竟又不是把所有钱都花完了,只是预算被压缩了。现在问题来了,冰箱里有 n 个种类的牛奶,它们有各自的数量 a_i​​ 和价格 b_i。作为一只学过动态规划的猫,Meowowco 一个月需要 m 盒牛奶,她想知道屯够一个月的牛奶量的最小开销。

                      思路:

定义一个结构体,按照费用从小到大排序,"kuku" (拟声词) 买牛奶就行了。

AC code:

#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
struct node{
	int a,b;
}milk[100005];
bool cmp(node a,node b){
	return a.b<b.b;
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>milk[i].a>>milk[i].b;
	}
	sort(milk+1,milk+n+1,cmp);
	for(int i=1;i<=n;i++){
		if(m==0) break;
		else{
			if(m>=milk[i].a){
				m-=milk[i].a;
				ans+=milk[i].a*milk[i].b; 
			}else{
				ans+=m*milk[i].b;
				m=0;
			}
		}
	}
	cout<<ans;
	return 0;
} 

           T2 ( trarry ) :

                      题目重现:

树组(Traary)是有序的树序列。

树组把树按有序的形式组织起来的一种形式。

这些有序排列的树的集合称为树组。

。。。。。。

Meowowco 有 n 棵树苗,今天要在数组的每一个位置种(物理)上一棵树。种好之后,我们称它为树组。

最开始,树组中所有的树的高度为 0。每天过后,每棵树会自然生长 1 单位高度。

Meowowco 的种树过程持续 m 天,在每一天早上,她有三种操作:

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

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

op=3:Meowowko 想知道该天某棵树 x 的高度。

对于每个 op=3,输出一个整数 h,代表该树的高度。

                      思路:

定义一个day,f数组,g数组,h数组,day表示过了多少天,f[x]表示x最后一次魔法是那一天开始,g[x]表示x最后一次魔法持续时间,h[x]表示x收到魔法影响额外生长的高度,最后对于每一个输出,输出day+h[x]即可。

AC code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m,k,day,f[N],g[N],h[N];
void over(int x){
	if(g[x]==0) return ;
	if(f[x]+g[x]-1<=day){
		h[x]+=g[x];
		g[x]=0;
	}else{
		int tmp=f[x]+g[x]-1-day;
		h[x]+=day-f[x]+1;
		g[x]=tmp;
		f[x]=day+1;
	}
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m>>k;
	while(m--){
		int op,x;
		cin>>op>>x;
		if(op==1){
			over(x);
			f[x]=day+1;
			g[x]=k;
		}else if(op==2){
			over(x);
			g[x]=0;
		}else{
			over(x);
			cout<<h[x]+day<<"\n";
		}
		day++;
	}
	return 0;
}

           T3 ( usagi ) :

                      题目重现:

Chino 是一个可爱的初中生,超喜欢兔子 (和 Cocoa) …!?精通咖啡,并且能干可靠。

今天 Chino 在梦境世界中被可爱的兔子环绕,它们都是这个梦境世界的卡密——Cocoa 的使徒。每一只棉花糖般的兔子都有一个可爱值 a_i

“超想和可爱的小兔子们贴贴 ∼∼”

因此她向 Cocoa 许愿:请让我挑选出一些可爱的兔子。

但是,Cocoa 并不希望 Chino 随意挑选兔子,她希望 Chino 挑选出的兔子的可爱值的和是 7 的倍数。Cocoa 作为这个世界的卡密,觉得仅有这一条挑选规则会让游戏变得很无趣,她制定规则的目的,很可能是,吃掉 Chino…!?于是她又增加了一条规则:

Cocoa 亲吻了所有的兔子,它们都受到了”祝福”,当 Chino 选择这只兔子之后,她将会获得祝福值 b_i。当 Chino 拥有的祝福值超过 H 点时,会被 Cocoa 吃掉。

Chino 想知道怎么样才能完成挑选可爱值和为 7 的倍数的兔子,可爱和最大的同时还不会被吃掉。

                       思路:

使用两个动规分别表示H=998244353和一般性H的时候的答案,为省时空间,使用滚动数组优化,并用位运算和奇偶性辅助滚动数组。(H=998244353时用的是01背包,一般性H时,用的是二维费用背包)

AC code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,H,a[10005],b[10005];
ll f[2][7],dp[2][1005][7];
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>H;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	if(H==998244353){
	    memset(f,-0x3f,sizeof f);
	    f[0][0]=0;
	    for(int i=1;i<=n;i++){
	        int t=i&1;
	        for(int j=0;j<7;j++) f[t][j]=max(f[t^1][j],f[t^1][((j-a[i])%7+7)%7]+a[i]);//t通过奇偶性辅助滚动数组,f(i,j)表示前i只兔子,可爱值之和取余7上限为j时的可爱值总和 。 
	    }
	    cout<<f[n&1][0];
	}else{
	    memset(dp,-0x3f,sizeof dp);
	    dp[0][0][0]=0;
	    for(int i=1;i<=n;i++){
	        int t=i&1;
	        for(int j=0;j<=H;j++){
	            for(int k=0;k<7;k++){
	                if(j<b[i]) dp[t][j][k]=dp[t^1][j][k];
	                else dp[t][j][k]=max(dp[t^1][j][k],dp[t^1][j-b[i]][((k-a[i])%7+7)%7]+a[i]);//t通过奇偶性辅助滚动数组,f(i,j)表示前i只兔子,祝福值上限为j, 可爱值之和取余7上限为k时的可爱值总和。 
	            }
	        }
	    }
	    ll ans=0;
	    for(int i=0;i<=H;i++) ans=max(ans,dp[n&1][i][0]);
	    cout<<ans;
	}
	return 0; 
}

           T4 ( missiles ) :

                      题目重现:

奥术飞弹是一个非指向性的技能,在施法前可以指定弹道,并对路径上第一个碰撞的目标造成伤害。

作为一颗成熟的奥术飞弹,你应该学会自己决定用于攻击目标的最短路径,并且 100\% 命中目标。

Meowowco 正在玩一款未知的 1 V 1 RTS 游戏,游戏创建后会随机创建一个有 n 个房间的地图,由 m 条通道相连,房间与房间之间最多只有一个通道,直接由通道相连的房间的距离可以记为 1,整张地图所有房间两两可达。

Meowowco 出生在编号为 1 的房间,而她的对手出生在编号为 n 的房间。现在 Meowowco 需要创造军队或者释放技能去击败对手,不过今天她有着更高级的黑魔法加持(指自瞄),令”奥术飞弹”变成”成熟的奥术飞弹”,只释放”奥术飞弹”就可以获得胜利。

作为一颗成熟的奥术飞弹,不管当前处于哪个房间,都会瞬间规划好 一条 前往目标所在位置的最短飞行弹道(当然,最短飞行弹道有时候并不是唯一的,所以有多条最短飞行弹道时会随机选择一条),如果它没有沿着当前房间规划好的最短飞行弹道飞行,记为偏离轨迹 1 次。

作为一颗成熟的奥术飞弹,应该会自己计算一条有着“大可能性“的飞行弹道。由于有些房间的最短飞行弹道不唯一,飞行弹道可能偏离也可能不偏离,”大可能性”飞行弹道要求 可能产生的偏离数尽可能大

一个房间如果有多条通往目标的最短飞行弹道,则可能偏离数增加 11,目标是找到有最多这样房间的飞行弹道。

下面将举个栗子来解释它:

对这个地图来说:

它有 3 条从 1 到 6 的最短飞行弹道:

  • 1=>2=>4=>6
  • 1=>3=>4=>6
  • 1=>3=>5=>6

 假如你现在处于房间 1,那么你可以选择房间 2 和房间 3 ,因为 2 和 3 都在最短飞行弹道上,所以不管前往哪个房间都会让可能偏离数增加,此时可能偏离数为 1

当飞弹选择 1=>2 时,接下来它只能沿着 2=>4=>6,飞行,因此这条弹道的可能偏离数为 1

当飞弹选择 1=>3 时,接下来的房间 4 和 5 都处于最短飞行弹道上,不管前往哪个房间都会让可能偏离数增加,此时可能偏离数增加到 2;之后都只有一条弹道到达 6,因此这两条弹道的可能偏离数为 2

综上所述,

  • 1=>2=>4=>6 的可能偏离数为 1
  • 1=>3=>4=>6 的可能偏离数为 2
  • 1=>3=>5=>6 的可能偏离数为 2

 最大的可能偏离数为 2

寻找出所有的最短飞行弹道,每条最短飞行弹道都有一个可能偏离数,找到其中最大的可能偏离数,输出最短飞行弹道条数和最大的可能偏离数。


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

                       思路:

正难则反:bfs(尾->头) 数组模拟队列 dis存储最短路  pre记录上一个点 tot存储最短路条数 ans存储最大偏移量   (详细见代码)

AC code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5,mod=998244353;
struct node{
	int to,nxt;
}edge[N<<2];
int n,m,cnt=1,head[N],hd=1,tail,q[N],dis[N],pre[N],tot[N],ans[N];
void add(int x,int y){
	edge[cnt].to=y;
	edge[cnt].nxt=head[x];
	head[x]=cnt++;
}
void bfs(){
	memset(dis,-1,sizeof dis);
	dis[n]=0;
	q[++tail]=n;
	while(hd<=tail){
		int u=q[hd++];
		for(int i=head[u];i;i=edge[i].nxt){
			int t=edge[i].to;
			if(dis[t]==-1){
				dis[t]=dis[u]+1;
				pre[t]=u;
				q[++tail]=t;
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	while(m--){
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	bfs();
	tot[n]=1;
	for(int i=1;i<=tail;i++){
		int u=q[i],flag=0;
		for(int j=head[u];j;j=edge[j].nxt){
			int v=edge[j].to;
			if(dis[u]==dis[v]+1){
				ans[u]=max(ans[u],ans[v]);
				(tot[u]+=tot[v])%=mod;
				if(v!=pre[u]) flag=1;
			}
		}
		ans[u]+=flag;
	}
	cout<<tot[1]<<" "<<ans[1];
	return 0;
}

五、没了没了,都散了吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值