一、题目分数
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 又来到了熟悉的超市,看着冰箱里陈列着价格不同的牛奶,她摸了摸自己的钱包,
“糟糕,出勤花了太多的钱了。。。。。。”
不过这都不是问题,毕竟又不是把所有钱都花完了,只是预算被压缩了。现在问题来了,冰箱里有 个种类的牛奶,它们有各自的数量
和价格
。作为一只学过动态规划的猫,Meowowco 一个月需要
盒牛奶,她想知道屯够一个月的牛奶量的最小开销。
思路:
定义一个结构体,按照费用从小到大排序,"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 有 棵树苗,今天要在数组的每一个位置种(物理)上一棵树。种好之后,我们称它为树组。
最开始,树组中所有的树的高度为 。每天过后,每棵树会自然生长
单位高度。
Meowowco 的种树过程持续 天,在每一天早上,她有三种操作:
:选择某棵树
对其施展魔法,该效果持续
天(包括当天)。拥有魔法效果的树每天晚上会额外生长
单位高度。若施展时该树已经存在魔法效果,则覆盖原来的魔法效果(也就是取消原来的魔法效果,加上这次的魔法效果)。
:选择取消某棵树
的魔法效果,可能会对没有施加魔法的树进行操作。
:Meowowko 想知道该天某棵树
的高度。
对于每个 ,输出一个整数
,代表该树的高度。
思路:
定义一个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 的使徒。每一只棉花糖般的兔子都有一个可爱值 。
“超想和可爱的小兔子们贴贴 ∼∼”
因此她向 Cocoa 许愿:请让我挑选出一些可爱的兔子。
但是,Cocoa 并不希望 Chino 随意挑选兔子,她希望 Chino 挑选出的兔子的可爱值的和是 的倍数。Cocoa 作为这个世界的卡密,觉得仅有这一条挑选规则会让游戏变得很无趣,她制定规则的目的,很可能是,吃掉 Chino…!?于是她又增加了一条规则:
Cocoa 亲吻了所有的兔子,它们都受到了”祝福”,当 Chino 选择这只兔子之后,她将会获得祝福值 。当 Chino 拥有的祝福值超过
点时,会被 Cocoa 吃掉。
Chino 想知道怎么样才能完成挑选可爱值和为 的倍数的兔子,可爱和最大的同时还不会被吃掉。
思路:
使用两个动规分别表示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 ) :
题目重现:
奥术飞弹是一个非指向性的技能,在施法前可以指定弹道,并对路径上第一个碰撞的目标造成伤害。
作为一颗成熟的奥术飞弹,你应该学会自己决定用于攻击目标的最短路径,并且 命中目标。
Meowowco 正在玩一款未知的 RTS 游戏,游戏创建后会随机创建一个有
个房间的地图,由
条通道相连,房间与房间之间最多只有一个通道,直接由通道相连的房间的距离可以记为
,整张地图所有房间两两可达。
Meowowco 出生在编号为 的房间,而她的对手出生在编号为
的房间。现在 Meowowco 需要创造军队或者释放技能去击败对手,不过今天她有着更高级的黑魔法加持(指自瞄),令”奥术飞弹”变成”成熟的奥术飞弹”,只释放”奥术飞弹”就可以获得胜利。
作为一颗成熟的奥术飞弹,不管当前处于哪个房间,都会瞬间规划好 一条 前往目标所在位置的最短飞行弹道(当然,最短飞行弹道有时候并不是唯一的,所以有多条最短飞行弹道时会随机选择一条),如果它没有沿着当前房间规划好的最短飞行弹道飞行,记为偏离轨迹 次。
作为一颗成熟的奥术飞弹,应该会自己计算一条有着“大可能性“的飞行弹道。由于有些房间的最短飞行弹道不唯一,飞行弹道可能偏离也可能不偏离,”大可能性”飞行弹道要求 可能产生的偏离数尽可能大:
一个房间如果有多条通往目标的最短飞行弹道,则可能偏离数增加 11,目标是找到有最多这样房间的飞行弹道。
下面将举个栗子来解释它:
对这个地图来说:
它有 条从
到
的最短飞行弹道:
假如你现在处于房间 ,那么你可以选择房间
和房间
,因为
和
都在最短飞行弹道上,所以不管前往哪个房间都会让可能偏离数增加,此时可能偏离数为
;
当飞弹选择 时,接下来它只能沿着
,飞行,因此这条弹道的可能偏离数为
。
当飞弹选择 时,接下来的房间
和
都处于最短飞行弹道上,不管前往哪个房间都会让可能偏离数增加,此时可能偏离数增加到
;之后都只有一条弹道到达
,因此这两条弹道的可能偏离数为
。
综上所述,
的可能偏离数为
的可能偏离数为
的可能偏离数为
最大的可能偏离数为 。
寻找出所有的最短飞行弹道,每条最短飞行弹道都有一个可能偏离数,找到其中最大的可能偏离数,输出最短飞行弹道条数和最大的可能偏离数。
简而言之,就是 个点的无向连通图,无重边和自环,目标是从
点到达
点。在一个点上,如果有多条最短路径到达终点,则 可能偏离数 加一,求出最短路径的总条数,以及所有最短路中可能偏离数的最大值。
思路:
正难则反: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;
}