【牛客】牛客小白月赛64 A-F 题解

206 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第31天,点击查看活动详情

【牛客】牛客小白月赛64 A-F 题解

A 小杜要迟到了!

题目大意

小杜现在在 11 楼,上课地点在 nn 楼。已知小杜走楼梯速度为 aa 秒每层楼,电梯运行速度为 bb 秒每层楼。然而最开始电梯停留在 kk,也就是说,电梯要先运行至 11才能接上小杜。

假设此时没有他人等电梯,且忽略电梯上下客的时间,且除了1楼外其他楼层不能呼叫电梯。

小杜想知道是走楼梯快还是坐电梯快还是一样快。

思路

步行上楼需要爬 n1n-1 层,用时为 (n1)×a(n-1)\times a

坐电梯除了需要上升 n1n-1 层外,还要等电梯下降 k1k-1 层,用时为 (k1)×b+(n1)×b(k-1)\times b+(n-1)\times b

比较即可。

代码

#include <stdio.h>
int main()
{
    int n,k,a,b;
    scanf("%d%d%d%d",&n,&k,&a,&b);
    int tw=(n-1)*a;
    int te=(k-1)*b+(n-1)*b;
    if (te<tw) printf("1");
    else if (te>tw) printf("2");
    else printf("0");
    return 0;
}

B 小杜捕鱼

题目大意

n×mn\times m 的池塘里有若干条鱼,用二维矩阵描述,每个格点是 . 表示该点没有鱼,是 # 表示该点有鱼。

在一个点撒网,能够网住所有距离该点曼哈顿距离小于 kk 的鱼。求最小的 kk 值,使得不论在哪个点撒网都可以网住池塘里所有鱼。

思路

反过来想,kk 其实是每个鱼到所有点的最远曼哈顿距离。

显然每个鱼到整个池塘四个角之一的曼哈顿距离最远,所以答案就是输出每个鱼到四个角距离的最大值。

代码

#include <stdio.h>
int main()
{
    int n,m,k=0;
    char ch;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j)
        {
            scanf(" %c",&ch);
            if (ch=='#')
            {
                if (i-1+j-1>k) k=i-1+j-1;
                if (n-i+j-1>k) k=n-i+j-1;
                if (i-1+m-j>k) k=i-1+m-j;
                if (n-i+m-j>k) k=n-i+m-j;
            }
        }
    printf("%d\n",k);
}

C Karashi的生日蛋糕

题目大意

蛋糕上水果摆放成 nn 圈,且第 ii 圈必须有 ii 个水果。

把蛋糕均分成 kk 块。要求如下:

  • 每块蛋糕包含第 ii 圈的水果数量为 ik\lfloor\frac{i}{k}\rfloorik\lceil \frac{i}{k}\rceil 个。
  • 并且任意两块蛋糕包含的水果总个数相差不得超过 1。

试构建一种水果的摆放方案,输出 kknn 列的非负整数矩阵,第 ii 行第 jj 列的数字 vali,jval_{i,j} 表示第 ii 块蛋糕上有 vali,jval_{i,j} 个第 jj 种水果。

思路

先把 ii 个水果平均分配给 kk 块蛋糕,即每块蛋糕先分 ik\lfloor\frac{i}{k}\rfloor 个第 ii 种水果,然后把余数循环分配给每一个人即可。

代码

#include <stdio.h>
#include <algorithm>
#include <vector>
using namespace std;
const int N=1000001;
int n,k;
vector<int> a[N];
int frt[N];
int main()
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<=k;++i)
    {
        a[i].push_back(0);
        for (int j=1;j<=n;++j) a[i].push_back(j/k);
    }
    int t=1;
    for (int i=1;i<=n;++i)
    {
        for (int j=1;j<=i%k;++j)
        {
            a[t][i]++;
            t=t%k+1;
        }
    }
    
    for (int i=1;i<=k;++i,printf("\n"))
        for (int j=1;j<=n;++j) printf("%d ",a[i][j]); 
    return 0;
}

D Karashi的树 I

题目大意

nn 个点以节点 1 为根的树每个点都有点权 aa,每个点 uu 的收益是它到根的的路径上所有节点的点权和。我们可以执行若干次下述操作:

  • 选择一个节点,使得它到根节点路径上的点权全部翻转,如下图所示:

image.png

执行若干次操作,最大化所有点的收益之和。

思路

容易猜到任意节点的权值都能变成其他的,所以建后统计每个节点子树的大小记为 sizsiz

假设节点 ii 的权值为 aia_i,且以节点 ii 为根的子树的大小为 sizisiz_i,那么就有 sizisiz_i 个节点的根路径上有节点 ii,则 aia_i 对答案的贡献为 ai×sizia_i\times siz_i

所以我们肯定希望权值大的节点有更多的子节点。所以答案是把 sizsiz 和权值 aa 分别排序后对应相乘再相加即可。

"任意节点的权值都能变成其他的,"
刚才我们说这个结论是猜到的。那么怎么证明呢?

比如一条链是:1-2-3-4-5
我们可以将其翻转变成:5-4-3-2-1
然后翻转前两个节点变成:4-5-3-2-1
然后再做一次整体翻转:1-2-3-4-5

这就说明了整棵树中任意两个相邻的节点可以交换,且不破坏除了这两个节点之外树其他部分的结构。所以整棵树上任意节点都可以取到任意值。

代码

#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;
const int N=300001;
int n,a[N],siz[N];
vector<int> e[N];
void dfs(int u,int fa)
{
    siz[u]=1;
    for (auto v:e[u])
    {
        if (v==fa) continue;
        dfs(v,u);
        siz[u]+=siz[v];
    }
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i) scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    for (int x,i=2;i<=n;++i)
    {
        scanf("%d",&x);
        e[x].push_back(i);
    }
    dfs(1,0);
    sort(siz+1,siz+1+n);
    long long ans=0;
    for (int i=1;i<=n;++i) ans+=1ll*siz[i]*a[i];
    printf("%lld",ans);
    return 0;
}

E Karashi的数组 I

题目

image.png

思路

sum=i=k+1k+lenaisum=\sum_{i=k+1}^{k+len} a_i,则式子

S(L)×S(R)=S(LR)×S(LR)S(L)\times S(R)=S(L\cup R)\times S(L\cap R)

就可以转化为

(sum+ak)×(sum+ak+len+1)=sum×(sum+ak+ak+len+1)sum2+(ak+ak+len+1)×sum+ak×ak+len+1=sum2+(ak+ak+len+1)×sumak×ak+len+1=0\begin{aligned} (sum+a_k)\times(sum+a_{k+len+1})&=sum\times(sum+a_k+a_{k+len+1})\\ sum^2+(a_k+a_{k+len+1})\times sum+a_k\times a_{k+len+1} &=sum^2+(a_k+a_{k+len+1})\times sum\\ a_k\times a_{k+len+1}&=0 \end{aligned}

所以 aka_kak+len+1a_k+len+1 至少有一个是 00 时,kk 对答案有贡献。

我们只需要先求一遍符合题意的 kk 的数量,每次修改将 axa_x 的值改为 yy,只会对两个区间产生影响,相应修改答案即可。

代码

#include <stdio.h>
const int N=500001;
int n,m,len,a[N];
int main()
{
    scanf("%d%d%d",&n,&m,&len);
    for (int i=1;i<=n;++i) scanf("%d",&a[i]);
    int cnt=0;
    for (int i=1;i+len+1<=n;++i)
        if (a[i]*a[i+len+1]==0) cnt++;
    for (int i=1,x,y;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        if (x+len+1<=n&&a[x]==0&&a[x+len+1]!=0) cnt--;
        if (x-len-1>=1&&a[x]==0&&a[x-len-1]!=0) cnt--;
            
        
        if (x+len+1<=n&&y==0&&a[x+len+1]!=0) cnt++;
        if (x-len-1>=1&&y==0&&a[x-len-1]!=0) cnt++;
        a[x]=y;
        printf("%d\n",cnt);
    }
    return 0;
}

F 小杜跑酷

题目

image.png

思路

容易想到 nn 很小的话,我们可以直接给陷阱打上标记,然后遍历前 n1n-1 列,设 dp[i][1/2/3]dp[i][1/2/3] 表示走到第 j=1/2/3j=1/2/3 行第 ii 列的方案数。很显然的转移如下:

  • 如果第 jj 行第 jj 列有机关,则
dp[i+1][max(1,j1)]+=dp[i][j]dp[min(i+2,n)][j]+=dp[i][j]dp[i+1][min(3,j+1)]+=dp[i][j]\begin{aligned} dp[i+1][max(1,j-1)]&+=dp[i][j]\\ dp[min(i+2,n)][j]&+=dp[i][j]\\ dp[i+1][min(3,j+1)]&+=dp[i][j] \end{aligned}
  • 否则
dp[i+1][j]+=dp[i][j]dp[i+1][j]+=dp[i][j]

虽然 nn 很大,但是如果第 ii 列没有机关,那么到第 i+1i+1 列的方案数将不需要经过上述转移。所以我们直接对所有输入的机关按列号排序进行 DP 即可。

代码

#include <stdio.h>
#include <algorithm>
using namespace std;
using LL=long long;
const LL mod=998244353;
const int N=500005;
struct asdf{
    int x,y;
    bool operator < (const asdf a) const
    {
        return y<a.y;
    }
}a[N];
int n,m,v[4];
LL f[4]={0,1,0,0};
LL g[4],h[4];
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;++i) scanf("%d%d",&a[i].x,&a[i].y);
    sort(a+1,a+1+m);
    for (int i=1;i<=m;++i)
    {
        v[a[i].x]=1;
        if (i<m&&a[i+1].y==a[i].y) continue;
        for (int j=1;j<=3;++j)
            if (v[j])
            {
            	if (a[i].y!=n-1) h[j]=(h[j]+f[j])%mod;
				else g[j]=(g[j]+f[j])%mod;
				
                if (j-1>0) g[j-1]=(g[j-1]+f[j])%mod;
                else g[j]=(g[j]+f[j])%mod;
                
                if (j+1<4) g[j+1]=(g[j+1]+f[j])%mod;
                else g[j]=(g[j]+f[j])%mod;
            }
            else g[j]=(g[j]+f[j])%mod;
            
        for (int j=1;j<=3;++j)
        {
            f[j]=g[j];
            g[j]=h[j];
            h[j]=v[j]=0;
        }
        if (a[i+1].y!=a[i].y+1)
        	for (int j=1;j<=3;++j) f[j]+=g[j],g[j]=0;
    }
    for (int j=1;j<=3;++j) printf("%lld\n",(f[j]+g[j])%mod);
    return 0;
}