CF1851 部分题解

发布时间 2023-09-08 10:39:07作者: NBest

2023-07-30 19:35:02

前言

因为我实在是太菜了,没时间也不会做最后两题,所以这里只有前 \(5\) 道签到题的题解。

之后我有时间看了后两题的题解再来更新吧~

A

先不用看那么多七七八八的,搞清楚下面几点即可:

  • 高度不能相同。
  • 高度差得被整除。
  • 高度差不能太大。
    好了,然后就可以 \(AC\) 了。

\(Code\)

int T,n,m,k,H,tot;
int main(){
    T=read();
    while(T--){
        n=read(),m=read(),k=read(),H=read(),tot=0;
        for(int i=1;i<=n;i++){
            int h=abs(H-read());
            if(h!=0&&h%k==0&&h/k<m)tot++;
        }
        printf("%d\n",tot);
    }
}

B

题意

给定序列,可以交换奇偶性相同的数字,问你最后可不可以交换成单调不降序列。

因为奇偶性不同是不可能换到位置相同的,即每个位置的奇偶性是固定的,只要在不改变位置奇偶性的状态下可以任意交换的,所以我们只需要保留序号排个序然后看排序之后自己原来位置填的是否是奇偶性相同的即可。

\(Code\)

typedef pair<int,int> PII;
int T,n,b[200005];
PII a[200005];
bool check(){
    for(int i=1;i<=n;i++)if((a[i].first&1)^(b[i]&1))return 0;
    return 1;
}
int main(){
    T=read();
    while(T--){
        n=read();
        for(int i=1;i<=n;i++){
            a[i].first=read();
            a[i].second=i;
            b[i]=a[i].first;
        }
        sort(a+1,a+1+n);
        if(check())puts("YES");
        else puts("NO");
    }
}

C

题意

你可以按顺序取出包含若干个色块的序列,序列必须包含第一块和最后一块,然后将其分割成若干个数量均为 \(k\) 的部分,每个部分的颜色必须一样,不同部分之间颜色可以一样,问你能否找到这样一个序列。

我们从第一块和最后一块必须选入手,实际上我们也只需要考虑最前一段和最后一段是否能顺序凑到 \(k\) 个即可。

因为无论中间如何选,最后选出的序列必然包含首尾段,所以我们实际上可以忽略中间的部分,用两个指针分别从前往后和从后往前扫一遍,判断前面和后面的段是否可以有超过 \(k\) 个颜色相同即可。

注意一些特判,比如当首尾颜色相同时只需要一段连起来即可。

\(Code\)

int T,n,c[200005],k;
bool check(){
    int l,r,tl=0,tr=0;
    for(r=1;r<=n;r++){
        if(c[r]==c[1])tr++;
        if(tr==k)break;
    }
    if(tr<k)return 0;
    for(l=n;l;--l){
        if(c[l]==c[n])tl++;
        if(tl==k)break;
    }
    if(tl<k)return 0;
    if(c[1]==c[n])return 1;
    if(r>l)return 0;
    return 1;
}
int main(){
    T=read();
    while(T--){
        n=read(),k=read();
        for(int i=1;i<=n;i++){
            c[i]=read();
        }
        if(check())puts("YES");
        else puts("NO");
    }
}

D

题意

给你一个前缀和数组,但是缺少了一个数值,问原数组能否是 \(n\) 的排列。

思路

首先遇到前缀和数组当然得先给它差分一下得到一个序列了,我们看到样例 \(4\),发现当少的是最后一个时,可以得到一个 \(n-1\) 的排列,这里特判即可。

那其他情况呢?在差分之后我们会发现 \([1,n]\) 至少少了两个值,可能会出现一个在 \([1,n]\) 中重复的,也可能出现 \(\le n\) 的,但是因为前缀和最后一个值不变,所以原序列的和也不变,故少的两个值必然相加等于那个不正常的值。

相应的,当少的数超过两个或者不正常的数不止一个,那么就不可能组成一个排列。

细节很多,结合代码理解更佳。

\(Code\)

int T,n;
ll a[200005];
bool pd[200005];
bool check(){
    ll los1=0,los2=0,mor=0;//los 少的数,mor是不正常的数
    for(int i=n-1;i;--i){
        a[i]-=a[i-1];
        if(a[i]>n||pd[a[i]]){
            if(mor)return 0;//不止一个不正常
            mor=a[i];
            continue;
        }
        pd[a[i]]=1;
    }
    for(int i=1;i<=n;i++){
        if(!pd[i]){
            if(!los1)los1=i;
            else if(!los2)los2=i;
            else return 0;//少的不止两个
        }
    }
    if(!los2&&!mor)return 1;//少的是最后一个值的情况
    if(los1+los2!=mor)return 0;//不相同也不行
    return 1;
}
int main(){
    T=read();
    while(T--){
        n=read();
        for(int i=1;i<n;i++){
            a[i]=read();
            pd[i]=0;
        }
        pd[n]=0;
        if(check())puts("YES");
        else puts("NO");
    }
}

E

第一眼以为是连边跑个最短路,没想到比跑最短路还简单。

由于一种魔药可以由其他的药合成,而且没有自己到的自己的方法,显然是个 DAG,可以用拓扑序一个个搞,但是我懒得打,直接跑个记搜即可。

关注到有无限的魔药,赋值为 \(0\) 即可,不能被合成的直接赋值成原价格,对于可以被合成的,我们向下搜索找价值,然后把总价格与自己的价格比较即可。

\(Code\)

int T,n,k,a[200005];
ll f[200005];
vector<int>F[200005];
ll dfs(int x){
    if(f[x]!=-1)return f[x];
    ll ans=0;
    for(int y:F[x]){
        ans+=dfs(y);
    }
    return f[x]=min(ans,1ll*a[x]);
}
int main(){
    T=read();
    while(T--){
        n=read(),k=read();
        for(int i=1;i<=n;i++){
            a[i]=read();
            f[i]=-1;
            F[i].clear();
        }
        for(int i=1;i<=k;i++){
            int p=read();
            a[p]=0;
            f[p]=0;
        }
        for(int i=1;i<=n;i++){
            int m=read();
            for(int j=1;j<=m;j++){
                int u=read();
                if(!a[i])continue;
                F[i].push_back(u);
            }
            if(!m)f[i]=a[i];
        }
        for(int i=1;i<=n;i++){
            printf("%lld ",dfs(i));
        }
        puts("");
    }
}