一如既往的没有题目大意(doge)
题目 | 牛奶(milk) | 树组(Traary) | 智乃的兔子(usagi) | 一颗成熟的奥术飞弹(missiles) |
赛中得分 | 100 | 30 | 5 | 10 |
赛后得分 | 100 | 100 |
题目分析
T1:牛奶(milk)
题目描述
每天一杯奶,健壮切题人。
Meowowco 有每天喝牛奶的习惯,因为牛奶是膳食中蛋白质、钙、磷、维生素 A、维生素 D 和维生素 B2 的重要来源之一,可以让经常出勤的 Meowowco 变得健壮。因此她每个月都会去买一些牛奶屯在冰箱里。
今天又到了采购的日子,Meowowco 又来到了熟悉的超市,看着冰箱里陈列着价格不同的牛奶,她摸了摸自己的钱包,
“糟糕,出勤花了太多的钱了。。。。。。”
不过这都不是问题,毕竟又不是把所有钱都花完了,只是预算被压缩了。现在问题来了,冰箱里有 n 个种类的牛奶,它们有各自的数量 ai 和价格 bi。作为一只学过动态规划的猫,Meowowco 一个月需要 m 盒牛奶,她想知道屯够一个月的牛奶量的最小开销。
输入描述
第一行二个整数 n,m,表示牛奶的种类和牛奶需求量。
接下来 nn 行,每行两个整数 ai,bi,表示第 i 种牛奶的数量(盒),和这种牛奶的单价(元)。
保证超市里牛奶的数量大于等于 Meowowco 的需求,即保证 ∑ai≥m。
输出描述
包含一个整数,表示 Meowowco 采购所需的牛奶所要的最小费用。
输入样例
5 100 20 5 40 9 10 3 80 8 30 6
输出样例
630
样例解释
10×3+20×5+30×6+40×8=30+100+180+320=630,即售价为 3、5、6 的牛奶全部购入,售价为 8 的牛奶购入 40 盒。
数据范围
对于 50% 的数据:1≤n≤10^3,1≤m≤10^3。
对于另外 50% 的数据:1≤n≤10^5,1≤m≤10^5。
对于所有的数据一定满足:1≤ai≤100,1≤bi≤10^5。
测试样例下载
milkhttps://kdyfiles.oss-cn-hangzhou.aliyuncs.com/kdycec0be6d189d4770b0e7903c6dacd24b.zip
赛中思路
结构体sort,然后没有然后了
正解代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m;
struct node{
int a,b;
}s[100005];
bool cmp(node x,node y){
if(x.b!=y.b)return x.b<y.b;
return x.a>y.a;
}
int main(){
//freopen("milk.in","r",stdin);
//freopen("milk.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)scanf("%d%d",&s[i].a,&s[i].b);
sort(s,s+n,cmp);
ll ans=0;
for(int i=0;i<n;i++){
if(m>=s[i].a){
ans+=1ll*s[i].a*s[i].b;
m-=s[i].a;
}else{
ans+=1ll*m*s[i].b;
m=0;
}
if(m<=0)break;
}printf("%lld",ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}
/*
5 100
20 5
40 9
10 3
80 8
30 6
*/
T2:树组(Traary)
题目描述
树组(Traary)是有序的树序列。
树组把树按有序的形式组织起来的一种形式。
这些有序排列的树的集合称为树组。。。。。。。
Meowowco 有 n 棵树苗,今天要在数组的每一个位置种(物理)上一棵树。种好之后,我们称它为树组。
最开始,树组中所有的树的高度为 0。每天过后,每棵树会自然生长 11 单位高度。
Meowowco 的种树过程持续 m 天,在每一天早上,她有三种操作:
op=1:选择某棵树 x 对其施展魔法,该效果持续 k 天(包括当天)。拥有魔法效果的树每天晚上会额外生长 1单位高度。若施展时该树已经存在魔法效果,则覆盖原来的魔法效果(也就是取消原来的魔法效果,加上这次的魔法效果)。
op=2:选择取消某棵树 x 的魔法效果,可能会对没有施加魔法的树进行操作。
op=3:Meowowko 想知道该天某棵树 x 的高度。
对于每个 op=3,输出一个整数 h,代表该树的高度。
输入描述
第一行输入两个整数 n,m,k。
接下来 m 行,第 i 行输入格式为 op,x,op∈1,2,3,1≤x≤n,代表第 i 天的操作。
输出描述
对于每个 op=3,输出一行一个整数 hh,代表该树的高度。
输入样例
7 9 3 3 1 3 1 1 5 1 5 1 1 3 1 2 1 3 1 3 5
输出样例
0 1 6 9 12
数据范围
对于 25% 的数据:1≤n≤1000,1≤m≤1000,1≤k≤100。
对于另外 25% 的数据:1≤n≤10^5,1≤m≤10^5,1≤k≤100。
对于另外 50% 数据:1≤n≤10^5,1≤m≤10^5,1≤k≤10^5。
测试样例下载
赛中思路
瞎写的一个能过样例的代码,本来也不指望能拿分(笑死)
赛中代码
(我不写了,太懒了(doge))
正解思路
可以开两个数组,一个用来存储magic使树非正常成长的高度,但是存储的为每一次的一整个k,第二个数组用于存储magic的持续时间
接着分类讨论就行
正解代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
ll n,m,k,op,x,day,a[N],b[N];
int main(){
memset(a,0,sizeof a);
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=m;i++){
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;
}
T3:智乃的兔子(usagi)
题目描述
Chino 是一个可爱的初中生,超喜欢兔子 (和 Cocoa) …!?精通咖啡,并且能干可靠。
今天 Chino 在梦境世界中被可爱的兔子环绕,它们都是这个梦境世界的卡密——Cocoa 的使徒。每一只棉花糖般的兔子都有一个可爱值 ai。
“超想和可爱的小兔子们贴贴 ∼∼”
因此她向 Cocoa 许愿:请让我挑选出一些可爱的兔子。
但是,Cocoa 并不希望 Chino 随意挑选兔子,她希望 Chino 挑选出的兔子的可爱值的和是 77 的倍数。Cocoa 作为这个世界的卡密,觉得仅有这一条挑选规则会让游戏变得很无趣,她制定规则的目的,很可能是,吃掉 Chino…!?于是她又增加了一条规则:
Cocoa 亲吻了所有的兔子,它们都受到了”祝福”,当 Chino 选择这只兔子之后,她将会获得祝福值 bi。当 Chino 拥有的祝福值超过 H 点时,会被 Cocoa 吃掉。
Chino 想知道怎么样才能完成挑选可爱值和为 7 的倍数的兔子,可爱和最大的同时还不会被吃掉。
输入描述
第一行包含两个正整数 n 和 H。
第二行包含 n 个非负整数 ai,表示每只兔子的可爱值。
第二行包含 n 个非负整数 bi,表示每只兔子的祝福值。
输出描述
输出单行,包含一个整数 S,表示挑选可爱值和为 7 的倍数的兔子,且不会被吃掉的最大可爱值和。
输入样例1
10 10 9 3 7 3 6 4 4 5 9 9 0 1 1 1 1 0 0 1 0 0
输出样例1
56
输入样例2
3 10 4 3 4 6 5 5
输出样例2
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。
测试样例下载
赛中思路
随手乱打的dp,不用看了
(我也不放代码了)
正解思路
大大滴dp
可爱值=价值,祝福值=重量
n件物品在容量h下的最大价值,且价值和为7的倍数
由于每件物品只能选择1次,因此可以
转换为01背包,并且多一维限制
dp[i][j][k]:容量j下选第i件物品得到%7余数为k的最大价值和
当h=998244353,j会炸,所以特判一下
其他包括转移方程等放一块代码
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;
}
但是如果是正常使用纯粹的a[10005][1005][8]肯定炸了,所以将第一维改成01,因为实际上求最大时只用上一个就行,所以可以得出以下代码:
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;
把上面两个代码整合一下,就能得出正解
正解代码
#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:一颗成熟的奥术飞弹(missiles)
题目背景
奥术飞弹是一个非指向性的技能,在施法前可以指定弹道,并对路径上第一个碰撞的目标造成伤害。
作为一颗成熟的奥术飞弹,你应该学会自己决定用于攻击目标的最短路径,并且 100% 命中目标。
题目描述
Meowowco 正在玩一款未知的 1V1 RTS 游戏,游戏创建后会随机创建一个有 nn 个房间的地图,由 m 条通道相连,房间与房间之间最多只有一个通道,直接由通道相连的房间的距离可以记为 1,整张地图所有房间两两可达。
Meowowco 出生在编号为 1 的房间,而她的对手出生在编号为 n 的房间。现在 Meowowco 需要创造军队或者释放技能去击败对手,不过今天她有着更高级的黑魔法加持(指自瞄),令”奥术飞弹”变成”成熟的奥术飞弹”,只释放”奥术飞弹”就可以获得胜利。
作为一颗成熟的奥术飞弹,不管当前处于哪个房间,都会瞬间规划好 一条 前往目标所在位置的最短飞行弹道(当然,最短飞行弹道有时候并不是唯一的,所以有多条最短飞行弹道时会随机选择一条),如果它没有沿着当前房间规划好的最短飞行弹道飞行,记为偏离轨迹 1 次。
作为一颗成熟的奥术飞弹,应该会自己计算一条有着“大可能性“的飞行弹道。由于有些房间的最短飞行弹道不唯一,飞行弹道可能偏离也可能不偏离,”大可能性”飞行弹道要求 可能产生的偏离数尽可能大:
一个房间如果有多条通往目标的最短飞行弹道,则可能偏离数增加 1,目标是找到有最多这样房间的飞行弹道。
下面将举个栗子来解释它:
对这个地图来说:
它有 33 条从 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 点。在一个点上,如果有多条最短路径到达终点,则 可能偏离数 加一,求出最短路径的总条数,以及所有最短路中可能偏离数的最大值。
输入描述
第一行输入两个正整数 n 和 m,表示房间个数和通道个数。
接下来 mm 行,每行包含两个正整数 u,v,表示房间 u 到房间 v 存在一条通道。
输出描述
输出最短飞行弹道条数和最大的可能偏离数,由于最短飞行弹道的条数可能很大,请对 998244353 取模。
输入样例1
6 7 1 2 3 4 2 4 1 3 3 5 4 6 5 6
输出样例1
3 2
输入样例2
7 8 1 2 1 3 3 4 2 4 4 5 4 6 5 7 6 7
输出样例2
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×n,保证图连通。
测试样例下载
赛中思路
本来想打个dfs,感觉不对又打了个bfs,最后干脆整合了,结果答案不对,时间不够,干脆随便敲了个东西(我也不知道是啥)就提交了(doge)
赛中代码
#include<bits/stdc++.h>
using namespace std;
queue<int>q;
const int mod=998244353;
int a[1005][1005],vis[1005],w[1005],s[1005][1005];
int n,m,u,v;
int len=0,ans=0;
void bfs(){
q.push(1);
w[1]=1;
vis[1]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=1;i<=n;i++){
if(!vis[i]&&a[x][i]){
q.push(i);
vis[i]=1;
w[i]=w[x]+1;
}
}
}
}
void dfs(int x){
if(x==n){
ans=(ans+1)%mod;
return;
}
for(int i=1;i<=n;i++){
if(a[x][i]&&!vis[i]&&(w[i]<w[n]||i==n)){
vis[i]=1;
dfs(i);
vis[i]=0;
}
}
}
int main(){
freopen("missiles.in","r",stdin);
freopen("missiles.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++){
scanf("%d%d",&u,&v);
a[u][v]=a[v][u]=1;
}bfs();
//for(int i=1;i<=n;i++)cout<<w[i]<<' ';
memset(vis,false,sizeof(vis));
vis[1]=1;
len=w[n];
dfs(1);
cout<<ans<<' '<<1;
fclose(stdin);
fclose(stdout);
return 0;
}
/*
6 7
1 2
3 4
2 4
1 3
3 5
4 6
5 6
*/
正解思路
由题目“简而言之”的描述可知,要求的是
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的最短路径数
所以说这么多还是得宽搜(doge)
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
}
}
}
}
接着就是计算,也是直接上代码,通过代码找思路
因为思路非常的麻烦,主要通过不断地样例来告诉我们,而身为一名蒟蒻,我自身就足够痛苦,时间又不够,无法像那些神犇一样通过几张图让我们大彻大悟、脱胎换骨、立地成佛,所以没办法,只能通过最直接的代码来陈述思路(人话:懒得想+懒得写)
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];
最后整合一下代码
代码,启动!!!!!!!!!!!!!!!!(虽迟到但从不离开)
正解代码
#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;
}