S27722-KDY-CSP二轮第五次解题报告

1.比赛情况

成绩
T1 牛奶T2 树组T3 智乃的兔子T4 一颗成熟的奥术导弹总分
ACTLE(30points 50?! )骗分(10points )骗分(10points)150points

2.赛中情况

第一题很简单,一道贪心水题,20min搞定。(差点写成DP)

第二题正解用的是模拟,被我打成了暴力,成功TLE,30。

第三、四题,逐渐变态。应该都是T4的难度。

另外,数据超过10^5的基本上都要用scanf,printf,不然会爆

3.题目分析

原题pdf点这里114514


T1 暴力出奇迹

题目描述

每天一杯奶,健壮切题人

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

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

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

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

输入描述

第一行二个整数 n,m,表示牛奶的种类和牛奶需求量。

接下来 nn 行,每行两个整数 ai,bi,表示第 ii 种牛奶的数量(盒),和这种牛奶的单价(元)。

保证超市里牛奶的数量大于等于 Meowowco 的需求,即保证 ∑ai≥m∑a​i​​≥m。

输出描述

包含一个整数,表示 Meowowco 采购所需的牛奶所要的最小费用。

输入样例

 
 
  1. 5 100
  2. 20 5
  3. 40 9
  4. 10 3
  5. 80 8
  6. 30 6

输出样例

 
 
  1. 630

样例解释

10×3+20×5+30×6+40×8=30+100+180+320=630即售价为 3、5、6 的牛奶全部购入,售价为 8 的牛奶购入 40 盒。

数据范围

对于 50% 的数据:1≤n≤10^3​​。

对于另外 50% 的数据:1≤n≤10^5,1≤m≤10^5。

对于所有的数据一定满足:1≤ai≤100,1≤bi≤10^5​​。

测试样例下载

milk

 分析:

文中提到了dp,实际上只是一个干扰项。

但其实只需仔细读题就能发现,题目只要求求最小代价,我们可以定义一个结构体。按照价格从小到大进行排序,进行选择即可

AC码:

#include<bits/stdc++.h>
using namespace std;
const int N=1005000;
struct qq{
	long long ge;
	long long v;
}a[N];
bool cmp(qq f,qq l){
	return f.v<l.v;
}
int main(){
	//freopen("milk.in","r",stdin);
	//freopen("milk.out","w",stdout);
	long long n,m,cnt=0,zac=0;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",&a[i].ge,&a[i].v);
	}sort(a+1,a+1+n,cmp);
	while(m){
		if(m>a[++cnt].ge){
			m-=a[cnt].ge;
			zac+=(a[cnt].v*a[cnt].ge);
		}else{
			zac+=(m*a[cnt].v);
			m=0;
		}
	}printf("%lld",zac);
	return 0;
}
/*
5 100
20 5
40 9
10 3
80 8
30 6
*/

T2

题目描述

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

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

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

。。。。。。

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

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

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

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

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

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

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

输入描述

第一行输入两个整数 n,m,kn,m,k。

接下来 mm 行,第 ii 行输入格式为 op,x,op∈1,2,3,1≤x≤nop,x,op∈1,2,3,1≤x≤n,代表第 ii 天的操作。

输出描述

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

输入样例

 
 
  1. 7 9 3
  2. 3 1
  3. 3 1
  4. 1 5
  5. 1 5
  6. 1 1
  7. 3 1
  8. 2 1
  9. 3 1
  10. 3 5

输出样例

 
 
  1. 0
  2. 1
  3. 6
  4. 9
  5. 12

数据范围

对于 25% 的数据:1≤n≤1000,1≤m≤1000,1≤k≤100

对于另外 25% 的数据:1≤n≤105,1≤m≤105,1≤k≤1001≤n≤10​5​​,1≤m≤10​5​​,1≤k≤100。

分析

 sub1


本题暴力方法可以按照时间线枚举,然后判断当前时间点是否产生魔法操作。但是模拟实现方法较为麻烦。


sub2


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

AC码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1005000;
ll a[N],b[N]={0};
ll n,m,k;
int main(){
	//freopen("traary.in","r",stdin);
	//freopen("traary.out","w",stdout);
	scanf("%lld%lld%lld",&n,&m,&k);
	for(int i=1;i<=m;i++){
		ll op,x;
		scanf("%lld%lld",&op,&x);
		if(op==3){
			printf("%lld\n",i-1+a[x]-max(1ll*0,b[x]-i));  //输出
		}else if(op==1){
			if(b[x]<=i)a[x]+=k,b[x]=i+k;    //魔法保存
			else a[x]+=k-(b[x]-i),b[x]=i+k;
		}else{
			if(b[x]<=i)continue;
			else a[x]-=b[x]-i,b[x]=0;  //防止重复计算
		}
	}
	return 0;
}
/*
7 9 3
3 1
3 1
1 5
1 5
1 1
3 1
2 1
3 1
3 5
*/

T3

题目描述

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

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

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

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

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

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

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

输入描述

第一行包含两个正整数 n 和 H。

第二行包含 n 个非负整数 a​i​​,表示每只兔子的可爱值。

第二行包含 n 个非负整数b​i​​,表示每只兔子的祝福值。

输出描述

输出单行,包含一个整数 S,表示挑选可爱值和为 7 的倍数的兔子,且不会被吃掉的最大可爱值和。

输入样例1

 
 
  1. 10 10
  2. 9 3 7 3 6 4 4 5 9 9
  3. 0 1 1 1 1 0 0 1 0 0

输出样例1

 
 
  1. 56

输入样例2

 
 
  1. 3 10
  2. 4 3 4
  3. 6 5 5

输出样例2

 
 
  1. 7

数据范围

对于测试点 1∼5:1≤n≤20,H=20。

对于测试点 6∼10:1≤n≤100,1≤H≤100。

对于前 50% 的数据:1≤ai≤10^4,0≤bi≤10。

对于测试点 11∼15:1≤n≤10000,H=998244353。

对于测试点 16∼20:1≤n≤10000,1≤H≤1000。

对于 100% 的数据:1≤ai≤10^9,0≤bi≤10。

测试样例下载

测试样例

分析 :

不难发现,前50%的数据完全可以枚举。在考场上可以先拿部分分。

要想AC,就得分析一下:

01背包增加限制:挑选可爱值和为7的倍数。

设 f(i,j,k) :表示前 i个物品,空间剩余j ,价%7 为 k。

但是本题需要注意初始化,例如: f(0,0,1) 是一定不合法的,需要初始化为负无穷,其余类似情况都需要清空。

方程:f[i][j][k]=max(f[i-1][j][k],f[i-1][j-b[i]][k-a[i]]

就出来了AC码:

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
/*可爱值=价值,祝福值=重量 
n件物品在容量h下的最大价值,且价值和为7的倍数
由于每件物品只能选择1次,因此可以
转换为01背包,并且多一维限制
dp[i][j][k]:容量j下选第i件物品得到%7余数为k的最大价值和
*/
const int N=1e5;
ll n,m,w[N],v[N],ans;
int main () {
	cin>>n>>m;  //h==重量m 
	for(int i=1;i<=n;i++)   cin>>v[i];  //可爱值=价值 
	for(int i=1;i<=n;i++)   cin>>w[i];  //祝福值=重量 
	if(m==998244353){//特殊容量,开数组放不下,	
		//但因为祝福值(重量)<=10可理解为背包容量无限
	    //因此dp[i][j][k]可以退化为dp[i][k]
	    ll dp[N][10]={0};
		for(int i=1;i<7;i++)	dp[0][i]=-1e18;
		//除dp[0][0]外,dp[0][其余]不存在 
		for(int i=1;i<=n;i++){
			for(int k=0;k<7;k++){
				dp[i][k]=max(dp[i-1][k],dp[i-1][((k-v[i])%7+7)%7]+v[i]);
				//((1-100)%7+7)%7 = (-99%7+7)%7=(-1+7)%7=6
                //((6-1)%7+7)%7 =(5%7+7)%7 =12%7=5
				//不放i,和放i前的最大价值和+v[i]
			}
		}
		cout<<dp[n][0];	//余数为0 
		return 0; 
	}
	ll dp[2][1005][10]={0};
	memset(dp,-0x3f3f,sizeof dp);
	dp[0][0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			for(int k=0;k<7;k++){
                if(j>=w[i])
				dp[i&1][j][k]=max(dp[!(i&1)][j][k],dp[!(i&1)][j-w[i]][((k-v[i])%7+7)%7]+v[i]);
				else dp[i&1][j][k]=dp[!(i&1)][j][k];
			}
		}
	}
	for(int i=0;i<=m;i++) ans=max(ans,dp[n&1][i][0]);
	cout<<ans;
	return 0;
}

T4

题目背景

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

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

题目描述

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

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

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

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

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

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

对这个地图来说:

image-20240918174721172

它有 33 条从 11 到 66 的最短飞行弹道:

11 => 22 => 44 => 66
11 => 33 => 44 => 66
11 => 33 => 55 => 66

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

当飞弹选择 11=>22 时,接下来它只能沿着 22 => 44 => 66,飞行,因此这条弹道的可能偏离数为 11。

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

综上所述,

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

最大的可能偏离数为 2。

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


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

输入描述

第一行输入两个正整数 nn 和 mm,表示房间个数和通道个数。

接下来 mm 行,每行包含两个正整数 u,v,表示房间 u 到房间 v 存在一条通道。

输出描述

输出最短飞行弹道条数和最大的可能偏离数,由于最短飞行弹道的条数可能很大,请对 998244353取模。

输入样例1

 
 
  1. 6 7
  2. 1 2
  3. 3 4
  4. 2 4
  5. 1 3
  6. 3 5
  7. 4 6
  8. 5 6

输出样例1

 
 
  1. 3 2

输入样例2

 
 
  1. 7 8
  2. 1 2
  3. 1 3
  4. 3 4
  5. 2 4
  6. 4 5
  7. 4 6
  8. 5 7
  9. 6 7

输出样例2

 
 
  1. 4 2

数据范围

对于测试点 1∼5:1≤n≤10。

对于测试点 6∼10:1≤n≤50。

对于测试点 11∼15:1≤n≤1000。

对于测试点 16∼20:1≤n≤100000。

对于 100% 的数据:n−1≤m≤2×nn−1≤m≤2×n,保证图连通。

测试样例下载

测试样例

分析: 

最短路计数问题。
第一次广搜
进行宽搜,记录出来dis 树组, 表示点i 到 n的最短路长度
第二次广搜
从 n为起点,宽搜每个点,可以求出n 到每个点的最短路,同时记 fa(i)表示i的父亲(宽搜时的前继点)。
假设 n点为6 点,那么 4的父亲就是6 , 5的父亲也是 6。
需要考虑:如何判断一个点到另一个点是否在最短路径上。例如:从3 到 4的边和从 4到 5的边是在最短路上的边,而从3 到1 并不是。
得出结论:从 x到y 的边在最短路可以看作从 x的最短距离等于y 的最短距离 +1。最终要求从1 到 n的最短路计数,可以离 n从近到远的考虑,枚举x,y ,如果dis+1=dis[x],说明x,y 在最短路边上,情况进行累加即可。

AC码:‘

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N=1e6+5,M=998244353;  //1e5运行错误
int n,m,cnt,step,head[N],v[2*N],nex[2*N]; 
int vis[N],dis[N],lu[N],fa[N],ans[N],num[N];
/* 由题目“简而言之”的描述可知,要求的是 
	1. 从1到n的最短路径有几条num[],分别可以怎么走
	2. 如果在某一点有多条最短路径到达终点,则偏离数+1
因此,本题为最短路径计数问题,使用宽搜来解决
	搜索过程中,需要记录路径信息(即怎么走的) lu[] 
若有多条最短路径,那么路径上的每个点到终点的距离都应该相同
	因此,想要知道某点与其他点是否都在最短路径(可能不同),可以用距离判断
	所以需要在搜索过程中记录距离,若从x能走到y,则dis[y]=dis[x]+1 
关于问题2的处理,可以先找一条最短路径作为预设最短路径,找到后再找其他点的路径
	如果该点在另一条最短路径上,但不是要走的点,即说明偏离ans[]
	因此,还需要在宽搜时,记录下路径上的每个点是从谁走到的 fa[]
因为最短路径是1到n,可以通过从n开始走,记录距离的方式找到不同最短路径 
  dis[i]记录最短路径上i->n的距离
  lu[i]宽搜时第i步走到的点
  fa[i]结点i的父节点,即从谁走到的结点i
  ans[i]走到i的偏离数
  num[i] 走到i的最短路径数 
*/
queue<int>q;
void add(int x,int y){
	v[++cnt]=y;
	nex[cnt]=head[x];
	head[x]=cnt;
}
void bfs(int x){
	q.push(x);
	vis[x]=1;
	while(!q.empty()){
		x=q.front();
		q.pop();
		lu[++step]=x;	//记录路径信息 
		if(x==1)	return ; 
		for(int i=head[x];i!=-1;i=nex[i]){
			if(!vis[v[i]]){
				vis[v[i]]=1;
				q.push(v[i]);	//走到的点入队 
				dis[v[i]]=dis[x]+1;//更新n~v[i]的距离 
				fa[v[i]]=x;//记录走到v[i]的点是x 
			}
		}
	}
}
int main(){
	memset(head,-1,sizeof head);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	bfs(n);	//方便记录,从终点开搜 
	num[n]=1;	//因为联通,所以到n的最短路径数至少为1 
	for(int i=1;i<=step;i++){	//预设最短路径的点 
		int u=lu[i],f=0;	//f是否偏离,u为预设路径的点 
		for(int j=head[u];j!=-1;j=nex[j]){	//找u的邻接点 
			if(dis[u]==dis[v[j]]+1){
			//预设路径上从v[j]到n的距离+1 == u到n的距离,说明v[j]在最短路径上 
				ans[u]=max(ans[u],ans[v[j]]);	//更新最大偏离 
				num[u]=(num[u]+num[v[j]])%M;	//更新最短路径数 
				//所有能到v[j]的点都能走到u,所以最短路径数累加 
				if(v[j]!=fa[u])	f=1;	//v[j]不是预设的最短路径点u走到的,偏离 
			}
		} 
		ans[u]+=f;	//更新u点的偏离数 
	}
	cout<<num[1]<<" "<<ans[1];
	return 0;
}
/*
6 7
1 2
3 4
2 4
1 3
3 5
4 6
5 6
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值