自学内容网 自学内容网

CSP-J模拟赛(5)补题报告

前言:

           1.牛奶(milk):100

           2.树组(Traary):10

           3,智乃的兔子(usagi):0

           4.一颗成熟的奥术飞弹(missiles):0

       总结一下,今天的题比正式考试难。没有200是因为第2题没想到O(N)的解法+实现有错误,3,4题没法骗分。复赛时要是AC前两题,再加上后两题能骗到分,是能拿一奖的(比较极限)。如果让我评价今天的考试的话,那就是:

 (这破题我不做也罢)

牛奶

题意:

冰箱里有 n个种类的牛奶,它们有各自的数量 ai 和价格 bi​​。作为一只学过动态规划的猫,Meowowco 一个月需要 m 盒牛奶,她想知道屯够一个月的牛奶量的最小开销。

考试回顾:

一开始想到背包(但我不会实现),研究一下发现O(N)就能过。其实不算特别难,主要是题目唬人。

题解:

数组+排序,每次累加最小的单价(注意开long long)。

AC: 
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
long long cnt,ans;//cnt计算价格,ans计算数量 
struct node{
int sl,dj;
}a[N]; 
bool cmp(node x,node y){
return x.dj<y.dj;
}
int main(){
//freopen("milk.in","r",stdin);
//freopen("milk.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].sl,&a[i].dj);
}
   sort(a+1,a+n+1,cmp);
   for(int i=1;i<=n;i++){
       if(ans>=m){
          break;
}
else{
if(m-ans<a[i].sl){
cnt+=a[i].dj*(m-ans);
ans+=(m-ans);
break;
}
else if(m-ans>=a[i].sl){
cnt+=a[i].dj*a[i].sl;
ans+=a[i].sl;

}
}
   }  
    printf("%lld",cnt);
return 0;
}

树组

题意:

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

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

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

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

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

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

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

考试回顾:

读题花了一些时间,想到了O(N^2)的大模拟,优化到了10^8左右。但正解是线性的O(N),在实现上 也有问题,没有AC。

题解:

day记录天数,对于每棵树的魔法进行分类讨论:(1)没有魔法(2)施过但是失效(3)还没施完

用f数组存起来,遇到op==3直接输出。可以做到O(1)单个操作维护。

AC: 
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+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){// 魔法结束天数小于等于day;
h[x]+=g[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(){
//freopen("traary.in","r",stdin);
    //freopen("traary.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    while(m--){//day:已经生长了的天数  g[x]:魔法的持续时间 
    int op,x;
    scanf("%d%d",&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);
printf("%d\n",h[x]+day);
}
day++;
}
return 0;
}

智乃的兔子

题意:

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

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

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

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

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

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

考试回顾:

尝试O(N*N*M)暴力骗分, TL意料之中。

题解:

分类讨论,当h极大时忽略祝福值,使用滚动数组优化二维费用背包;当h正常时,二维费用背包变三维费用背包(f (i,j,k)意为第i个兔子的祝福值不超过j,它%7的数是k),也用滚动数组加位运算进行优化。

AC: 
#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=1e5+5;
int n,h,a[10005],b[10005];
ll f[2][7],dp[2][1005][7];
int main(){
//freopen("usagi.in","r",stdin);
    //freopen("usagi.out","w",stdout);
   scanf("%d%d",&n,&h);
   for(int i=1;i<=n;i++) scanf("%d",&a[i]);
   for(int i=1;i<=n;i++) scanf("%d",&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]);
           }
       }
       printf("%lld\n",f[n&1][0]);
       return 0;
   }
   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]);//状态转移方程
           }
       }
   }
   ll ans=0;
   for(int i=0;i<=h;i++){
       ans=max(ans,dp[n&1][i][0]);
   }
   printf("%lld",ans);
return 0;
}

一颗成熟的奥术飞弹

题意:

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

考试回顾:

使用bfs暴搜+加计数,时间超限没有悬念(怎么3,4都时超了呢,是我解法太暴力?)。

题解:

    从n至1进行宽搜(用数组模拟队列),得到最短路径以及每个点的下一步(因为倒着搜,每个点的后继就是它的前驱)再遍历存好的数组,判断一个点是否在另一个点的最短路径上,可以得出结论:从 x到 y的边在最短路可以看作从x 的最短距离等于 y的最短距离 +1。最终要求从 到 的最短路计数,可以离 从近到远的考虑,枚举(x , y) ,如果dis x=dis y+1,说明(x , y) 在最短路边上,情况进行累加,cnt x += cnt y。

AC: 
#include<bits/stdc++.h>
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 u,int v){
edge[cnt].to=v;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
void bfs(){//数组模拟队列,从n搜到1。 
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 v=edge[i].to;
if(dis[v]==-1){
dis[v]=dis[u]+1;
pre[v]=u;
q[++tail]=v;
}
}
}
}
int main(){ 
//freopen("missiles.in","r",stdin);
    //freopen("missiles.out","w",stdout);
    scanf("%d%d",&n,&m);
    while(m--){
    int u,v;
    scanf("%d%d",&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;
}
printf("%d %d\n",tot[1],ans[1]);
return 0;
}


原文地址:https://blog.csdn.net/CXP12636/article/details/142713802

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!