牛客练习赛56 题解

前言

菜鸡\(OIer\)没有写出\(F\)题来\(qwq\),想看\(F\)题题解的\(dalao\)们可以离开了。

A 小蒟和他的乐谱

题目

小蒟上音乐课的时候,老师说宫商角徵羽(分别对应\(C\)大调的\(do,re,mi,sol,la\))五个音是乐音,它们和它们升降任意个八度的得到音是好听的音(即高音\(do\)、低音\(mi\)等也是好听的音),用好听的音谱的曲会很好听。
小蒟觉得他的老师说得对,于是他打开了一本乐谱,随便找了一首曲子,他想知道这首曲子的好听程度。
小蒟太蒻了,善良的你不得不帮助他。
注:
一首曲子是一个整数序列,数字表示音高,\(1\sim 7\)分别代表\(C\)大调的\(do,re,mi,fa,sol,la,si\),8代表高音\(do\)(即\(\dot{1}\)),0代表低音\(si\),15代表\(\dot{ \dot{1} }\) ,-123表示很低很低的\(mi\),以此类推。
曲子的好听程度定义为曲子中最长的全部由好听的音组成的子串的长度。
对于\(100\%\)的数据,\(1≤ n ≤ 1,000,000,-10^9≤ A_i ≤ 10^9\)

思路

送分题。
直接判断\((x\% 7+7)\% 7\)是不是1,2,3,5,6中的一个即可。

代码

#include 
#include 
#include 
#include 
using namespace std;

const int N=1000010;
int n,m,b[N],a[N],cnt,ans;

int main()
{
    scanf("%d",&n);
    for (int i=1,p;i<=n;i++)
    {
        scanf("%d",&p);
        p=(p%7+7)%7;
        if (p==1||p==2||p==3||p==5||p==6) cnt++;
        else
        {
            ans=max(ans,cnt);
            cnt=0;
        }
    }
    printf("%d",ans);
    return 0;
}

B 小琛和他的学校

题目

小琛是一所学校的校长。
他的学校有\(n\)个校区(编号\(1\sim n\)),被\(n-1\)条双向道路连接,呈树形结构。
\(i\)个校区共有\(A_i\)个学生。
\(i\)天早上,所有的学生会沿最短路走到第\(i\)个校区参加活动,晚上再原路返回。
一个人通过第\(j\)条通道一次(即一人次),需要小琛支付\(w_j\)的维护费用。
小琛想知道第\(n\)天结束之后,对于每一条通道,他总共需要支付多少费用。
对于\(100\%\)的数据,\(1≤ n ≤ 200,000,1≤ A[i]≤ 10,000,1≤ w[i] ≤ 10,000\)

思路

还是送分题。
维护一下以一为根时,每一个点的大小\(ss[x]\),以及每一个点为根的子树内有多少个人\(size[x]\)
然后对于每一个点\(x\),它与它父节点\(fa\)的边就会有\(ss[x]\times (sum-size[x])+(n-ss[x])\times size[x]\)人次单向通过。
然后这条边的答案就是上式\(\times 2\times dis(v,fa)\)

代码

#include 
#include 
#include 
using namespace std;
typedef long long ll;

const int N=200010;
int n,m,tot,head[N];
ll ans[N],sum,size[N],ss[N];

struct edge
{
    int next,to,dis,id;
}e[N*2];

void add(int from,int to,int dis,int x)
{
    e[++tot].to=to;
    e[tot].id=x;
    e[tot].dis=dis;
    e[tot].next=head[from];
    head[from]=tot;
}

void dfs1(int x,int fa)
{
    ss[x]=1;
    for (int i=head[x];~i;i=e[i].next)
    {
        int v=e[i].to;
        if (v!=fa)
        {
            dfs1(v,x);
            size[x]+=size[v];
            ss[x]+=ss[v];
        }
    }
}

void dfs2(int x,int fa)
{
    for (int i=head[x];~i;i=e[i].next)
    {
        int v=e[i].to;
        if (v!=fa)
        {
            ans[e[i].id]=1LL*size[v]*(n-ss[v])+1LL*(sum-size[v])*ss[v];
            dfs2(v,x);
        }
    }
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%lld",&size[i]);
        sum+=size[i];
    }
    for (int i=1,x,y,z;i

C 小魂和他的数列

题目

一天,小魂正和一个数列玩得不亦乐乎。
小魂的数列一共有\(n\)个元素,第\(i\)个数为\(A_i\)
他发现,这个数列的一些子序列中的元素是严格递增的。
他想知道,这个数列一共有多少个长度为K的子序列是严格递增的。
请你帮帮他,答案对\(998244353\)取模。
对于\(100\%\)的数据,\(1≤ n ≤ 500,000,2≤ K ≤ 10,1≤ A_i ≤ 10^9\)

思路

先离散化。
\(f[i][j]\)表示以(数字)\(i\)结尾的长度为\(j\)的严格递增子序列的个数。
那么枚举序列\(a\),假设现在枚举到第\(k\)位,那么需要更新的只有\(f[a[k]][1\sim m]\)
那么显然有
\[f[a[k]][p]=\sum^{q
这样我们就得到了一个\(O(n^2k)\)的优秀算法,在\(ACM\)赛制下获得\(0pts\)的高分。
我们发现每次转移是一个前缀和,考虑用树状数组维护前缀和。
\(k\)棵树状数组,第\(i\)棵表示严格递增序列长度为\(i\)的数量。
那么\(\sum^{q
时间复杂度\(O(nk\log n)\)

代码

#include 
#include 
#include 
using namespace std;

const int N=500010,M=11,MOD=998244353;
int n,m,a[N],b[N],f[N][M],cnt[N],cpy[M];

struct Bit
{
    int c[N];
    
    void add(int x,int val)
    {
        for (int i=x;i<=n;i+=i&-i)
            c[i]=(c[i]+val)%MOD;
    }
    
    int ask(int x)
    {
        int ans=0;
        for (int i=x;i;i-=i&-i)
            ans=(ans+c[i])%MOD;
        return ans;
    }
}bit[M];

int main()
{
    for (int i=0;i<=10;i++)
        memset(bit[i].c,0,sizeof(bit[i].c));
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+1+n);
    int totel=unique(b+1,b+1+n)-b-1;
    for (int i=1;i<=n;i++)
        a[i]=lower_bound(b+1,b+1+totel,a[i])-b;
    for (int i=1;i<=n;i++)
    {
        cnt[a[i]]++;
        f[a[i]][1]=cnt[a[i]];
        bit[1].add(a[i],1);
        for (int j=2;j<=m;j++)
        {
            cpy[j]=bit[j-1].ask(a[i]-1);
            f[a[i]][j]=(f[a[i]][j]+cpy[j])%MOD;
        }
        for (int j=1;j<=m;j++)
            bit[j].add(a[i],cpy[j]);
    }
    int ans=0;
    for (int i=1;i<=n;i++)
        ans=(ans+f[i][m])%MOD;
    printf("%d",ans);
    return 0;
}

D 小翔和泰拉瑞亚

题目

小翔爱玩泰拉瑞亚 。
一天,他碰到了一幅地图。这幅地图可以分为\(n\)列,第\(i\)列的高度为\(H_i\),他认为这个地图不好看,决定对它进行改造。
小翔又学会了\(m\)个魔法,实施第\(i\)个魔法可以使地图的第\(L_i\)列到第\(R_i\)列每一列的高度减少\(W_i\),每个魔法只能实施一次,魔法的区间可能相交或包含。
小翔认为,一幅地图中最高的一列与最低的一列的高度差越大,这幅地图就越美观。
小翔可以选择\(m\)个魔法中的任意一些魔法来实施,使得地图尽量美观。但是他不知道该如何选择魔法,于是他找到了你。请你求出所有可行方案中,高度差的最大值。
对于\(100\%\)的数据,满足\(1≤n,m≤200000,-10^9≤H_i≤10^9,1≤W_i≤10^9,1≤L_i≤R_i≤n\)

思路

先把所有操作进行,然后就相当于每次选择一个操作将其撤回,也就是将这个操作的区间加上这一个数。
如果我们已经知道一个位置\(x\)最终会是最大值了,那么我们肯定把所有包含\(x\)的区间撤回,如果最小值在这个区间,那么最大值与最小值只差不变,不影响结果;如果最小值不在这个区间,结果会变大。所以撤回所有\(x\)所在区间会最优。
但是我们并不知道最大值是谁,所以考虑枚举最大值。
我们发现,如果我们已经知道\(x\)为最大值时每一个位置的数值了,那么我们要推到位置\(x+1\)为最大值时,只需要把所有右端点为\(x\)的区间减去,左端点为\(x+1\)的区间加上。这样每一个修改总共只会便利两次。
所以先暴力求出位置\(1\)为最大值时的答案,然后把此时每一个位置的数值扔进一棵线段树内,然后每次区间修改,在所有答案中取一个最大值即可。
时间复杂度\(O(n\log n)\)

代码

#include 
#include 
#include 
using namespace std;
typedef long long ll;

const int N=200010;
int n,m,head,tail;
ll ans,b[N],a[N],s[N];

struct node
{
    int l,r,val;
}q1[N],q2[N];

bool cmp1(node x,node y)
{
    return x.l>1;
        build(x*2,l,mid); build(x*2+1,mid+1,r);
        pushup(x);
    }
    
    void update(int x,int l,int r,int val)
    {
        if (tree[x].l==l && tree[x].r==r)
        {
            tree[x].minn+=val;
            tree[x].lazy+=val;
            return;
        }
        pushdown(x);
        int mid=(tree[x].l+tree[x].r)>>1;
        if (r<=mid) update(x*2,l,r,val);
        else if (l>mid) update(x*2+1,l,r,val);
        else update(x*2,l,mid,val),update(x*2+1,mid+1,r,val);
        pushup(x);
    }
}Tree;

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        b[i]=a[i];
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&q1[i].l,&q1[i].r,&q1[i].val);
        s[q1[i].l]+=1LL*q1[i].val; s[q1[i].r+1]-=1LL*q1[i].val;
        q2[i].l=q1[i].l; q2[i].r=q1[i].r; q2[i].val=q1[i].val;
    }
    for (int i=1;i<=n;i++)
    {
        s[i]+=s[i-1];
        a[i]-=s[i];
    }
    Tree.build(1,1,n);
    sort(q1+1,q1+1+m,cmp1);
    sort(q2+1,q2+1+m,cmp2);
    head=1;
    for (;q1[tail+1].l==1;tail++)
        Tree.update(1,q1[tail+1].l,q1[tail+1].r,q1[tail+1].val);
    ans=b[1]-Tree.tree[1].minn;
    for (int i=2;i<=n;i++)
    {
        for (;q1[tail+1].l==i;tail++)
            Tree.update(1,q1[tail+1].l,q1[tail+1].r,q1[tail+1].val);
        for (;q2[head].r==i-1;head++)
            Tree.update(1,q2[head].l,q2[head].r,-q2[head].val);
        ans=max(b[i]-Tree.tree[1].minn,ans);
    }   
    printf("%lld",ans);
    return 0;
}

E 小雀和他的王国

题目

年纪轻轻的小雀当上了国王。
小雀的王国中一共有\(n\)座城市(编号为\(1\sim n\)),被\(m\)条双向的高速公路连接,任意两个城市之间都可以通过若干条高速公路互相到达。
但是在小雀的王国里,经常发生自然灾害。一次突发的自然灾害会随机破坏一条高速公路,并且有可能使得某两个城市之间无法到达彼此,这样整个王国就不能继续正常运转了。小雀为此很是苦恼。
于是小雀决定再修建一条高速公路,连接某两个城市,使得下一次突发自然灾害的时候,整个王国不能继续正常运转的概率最小。但是他不知道该如何选择新高速公路所连接的两个城市。
请你帮助他选择新建高速的方案,并求出新的高速公路建成之后,突发自然灾害时,整个王国不能继续正常运转的最小概率。答案对\(10^9+7\)取模。
对于\(100\%\)的数据,\(2≤ n,m ≤ 200000\)

思路

明显,当且仅当一条边是桥时,删去这条边才会把原图分成多个连通块。
所以我们先把这张图缩点,这样的一棵树中,删除任意一条边就会使原图不连通。
此时我们需要加入一条边,使得加入这条边后,这棵基环树的桥最少,也就是环上的点尽量多,其实就是缩点后的树的直径了。
用缩点后的图的点数减去树的直径,即为最终的桥的最少数量\(s\)
此时的答案即为\(\frac{s}{m+1}\),快速幂跑一波即可。
时间复杂度\(O(n)\)

代码

#include 
#include 
#include 
using namespace std;
typedef long long ll;

const int N=200010,MOD=1e9+7;
int n,m,tot=1,cnt,maxlen,f[N],low[N],dfn[N],head[N],pos[N],U[N],V[N];
bool brg[N*2],vis[N];

struct edge
{
    int next,to;
}e[N*2];

void add(int from,int to)
{
    e[++tot].to=to;
    e[tot].next=head[from];
    head[from]=tot;
}

void tarjan(int x,int in)
{
    dfn[x]=low[x]=++tot;
    for (int i=head[x];~i;i=e[i].next)
    {
        int y=e[i].to;
        if (!dfn[y])
        {
            tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if (low[y]>dfn[x])
            {
                brg[i]=brg[i^1]=1;
                cnt++;
            }
        }
        else if (i!=(in^1)) low[x]=min(low[x],dfn[y]);
    }
}

void dfs(int x,int fa,int id)
{
    if (vis[x]) return;
    pos[x]=id; vis[x]=1;
    for (int i=head[x];~i;i=e[i].next)
        if (e[i].to!=fa && !brg[i]) dfs(e[i].to,x,id);
}

void dp(int x,int fa)
{
    for (int i=head[x];~i;i=e[i].next)
    {
        int y=e[i].to;
        if (y!=fa)
        {
            dp(y,x);
            maxlen=max(maxlen,f[x]+f[y]+1);
            f[x]=max(f[x],f[y]+1);
        }
    }
}

ll power(ll x,ll k)
{
    ll ans=1;
    for (;k;x=x*x%MOD,k>>=1)
        if (k&1) ans=ans*x%MOD;
    return ans;
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for (int i=1,x,y;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
        U[i]=x; V[i]=y;
    }
    tot=0; tarjan(1,0); tot=0;
    for (int i=1;i<=n;i++)
        if (!vis[i]) dfs(i,0,++tot);
    memset(head,-1,sizeof(head)); tot=0;
    for (int i=1;i<=m;i++)
        if (pos[U[i]]!=pos[V[i]])
            add(pos[U[i]],pos[V[i]]),add(pos[V[i]],pos[U[i]]);
    dp(1,0);
    cnt-=maxlen;
    printf("%lld",1LL*cnt*power(m+1,MOD-2)%MOD);
    return 0;
}

你可能感兴趣的:(牛客练习赛56 题解)