牛客小白赛 65 题解

牛客小白赛 65

A. 牛牛去购物

标签

统计类 DP

思路

  1. 因为值域很少,故可设 \(dp_i\) 表示 \(i\) 是否出现过。则初始化为 \(dp_{0}=1\),之后分别\(1\)\(x\) 进行转移方程 \(dp_i|=dp_{i-x},i-x\ge 0\),其中 \(x=a,b\)
  2. 时间复杂度为 \(\mathcal O(T)\)\(T\) 为值域。

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int dp[maxn],n,a,b;
int main()
{
    cin>>n>>a>>b;
    dp[0]=1;
    for(int i=a;i<=n;i++)
        dp[i]|=dp[i-a];
    for(int i=b;i<=n;i++)
        dp[i]|=dp[i-b];
    int ans=0;
    for(int i=1;i<=n;i++)
        if(dp[i])ans=max(ans,i);
    printf("%d",n-ans);
    return 0;
}

B. 牛牛写情书

标签

KMP 字符串

思路

  1. 将特殊字符的 ascii 码统计下来,遍历字符串 \(a\),将非特殊字符加入到新字符串 \(a'\) 中。
  2. \(b\) 为模式串,\(a'\) 为主串进行 KMP
  3. 时间复杂度为 \(\mathcal O(\max(len_a,len_b))\)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e3+5;
const char consts[39]={'0', '1', '2', '3', 
                '4', '5', '6', '7',
                '8', '9', '+', '-',
                '*', '|', ',', '.',
                '~', '!', '@', '#',
                '$', '%', '^', '&', 
                '(', ')', '[', ']',
                '{', '}', '"', ';', 
                ':', '?', '<', '>',
                '\\', '/'};
char a[maxn],b[maxn],sa[maxn];
int n,m,nxt[maxn],vis[300],ni;
int main()
{
    for(int i=0;i<39;i++)
        vis[int(consts[i])]=1;
    vis[39]=1;
    scanf("%d%d",&n,&m);
    cin>>a+1>>b+1;
    for(int i=1;i<=n;i++)
        if(!vis[int(a[i])]) 
            sa[++ni]=a[i];
    for(int i=2,j=0;i<=m;i++)
    {
        while(j&&b[i]!=b[j+1])
            j=nxt[j];
        j+=(b[i]==b[j+1]);
        nxt[i]=j;
    }
    for(int i=1,j=0;i<=ni;i++)
    {
        while(j&&sa[i]!=b[j+1])
            j=nxt[j];
        j+=(sa[i]==b[j+1]);
        if(j==m)
        {
            cout<<"YES";
            return 0;
        }
    }
    printf("NO");
    return 0;
}

C. 牛牛排队伍

标签

链表 模拟

思路

  1. 链表板子题。
  2. 时间复杂度为 \(\mathcal O(n)\)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int n,k;
struct people
{
    int next,from;
} p[maxn];
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        p[i].from=i-1;
        p[i].next=i+1;
    }
    int op,x;
    while(k--)
    {
        scanf("%d%d",&op,&x);
        if(op==2) printf("%d\n",p[x].from);
        else
        {
            p[p[x].from].next=p[x].next;
            p[p[x].next].from=p[x].from;
        }
    }
    return 0;
}

D. 牛牛取石子

标签

博弈论

思路

  1. 首先考虑一次性结束游戏的状态。\(a=1,b=1\),则 \(niumei\);若 \(2<a+b\le4\),则 \(niuniu\) 胜。
  2. 考虑多次游戏的情况。发现 $(1,2)+(2,1)=(3,3) $,故后手总可以使得每一轮操作后使得两边石子数均减少 \(3\)。若 \(a\ne b\),设较少的一堆为 \(x\)则:若 \(3|x\),则后手必胜,此时后手为 \(niumei\);若 $x%3=1 $,则 \(niuniu\) 令数值为 \(x\) 的堆减一,易验证另一堆减少后的数量一定不小于 \(x-1\),此时后手必胜,后手为 \(niuniu\);若 \(a\%3=2\),同理可得,\(niuniu\) 必胜。若 \(a=b\),则:若 \(3|a\),显然 \(niumei\) 胜;若 $a%3=1 $,显然 \(niumei\) 胜;若 $a%3=1 $,此时 \(niumei\) 一定知道每一轮都让两边石子按照 \(3\) 递减不合适,故其会在 \(niuniu\) 操作后采取与其相同的操作,导致较小的一堆在模 \(3\) 下等于 \(1\),此时后手必胜,后手为 \(niuniu\)
  3. 时间复杂度为 \(\mathcal O(T)\)

代码

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a,b;
int T;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld",&a,&b);
        if(a==1&&b==1)
        {
            printf("niumei\n");
            continue;
        }
        if(a==1||b==1||a==2||b==2) printf("niuniu\n");
        else
        {
            if(a!=b)
            {
                ll x=min(a,b);
                if(x%3==0) printf("niumei\n");
                else printf("niuniu\n");
            }
            else
            {
                if(a%3==0) printf("niumei\n");
                else if(a%3==1) printf("niumei\n");
                else printf("niuniu\n");
            }
        }
    }
    return 0;
}

E. 牛牛的构造

标签

构造

思路

  1. 因为满足要求的数对与次序有关,故先考虑极端情况——递增与递减序列。考虑后易得,递增数列任意数对答案的贡献均为 \(0\),递减数列数列可以满贡献,且其满贡献的实质是所有比他小的数均在其后面。故可以考虑构造 \(a\) 的形式为递减数列+递增数列,因前面为递减数列,故可以保证递减数列中的任意元素满贡献。
  2. 考虑某个数 \(i\) 的以 \(i\) 为数对中大数的满贡献 \(dp_i\) 是多少。容易发现,与之贡献的数 \(x\),有 \(i-x=2^y\),进而有 \(x=i-2^y,y\ge 0\),故 \(dp_i=[\log_{2}^{i-1}]+1\)。发现 \(dp\) 数列中的数连续
  3. 基于 \(dp\) 中的数连续,可构造数列 \(a\)。若 \(\sum\limits_{1}^{n}dp_i < k\),则无法构造。否则,设 \(res\) 表示目前剩余的需要构造的数对数,从 \(n\)\(1\) 遍历 \(dp\),若 \(dp_i\le res\),则将 \(i\) 压入递减数列,\(res\) 减少 \(dp_i\);否则 \(i\) 压入递增数列。因为 \(dp\) 连续,故总可以使得 \(res\)\(0\)
  4. 时间复杂度为 \(\mathcal O(n)\)

代码

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxa=1e6+5;
int n,k,inc[maxa],d[maxa],p,pi;
ll dp[maxa],sum;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i<<=1)
        dp[i+1]=1;
    for(int i=1;i<=n;i++)
        dp[i]=dp[i-1]+dp[i],
        cout<<i<<" "<<dp[i]<<endl;
    ll num=0;
    for(int i=1;i<=n;i++)
        num+=dp[i];
    if(num<k) printf("-1");
    else
    {
        for(int i=n;i>=1;i--)
        {
            if(k<dp[i]) inc[++p]=i;
            else 
            {
                d[++pi]=i,k-=dp[i];
            }
        }
        for(int i=1;i<=pi;i++)
            cout<<d[i]<<" ";
        for(int i=p;i>=1;i--)
            cout<<inc[i]<<" ";
    }
    return 0;
}

F. 牛牛的考试

标签

贪心 树上 DP

思路

  1. \(tot_i\) 表示以 \(i\) 为根节点的整颗子树的权值和,\(ans_i\) 表示 完成整颗子树所需的最少时间。根据贪心,可知应当使双课程同时进行的值尽可能的大。故我们假设单位时间为 \(1\) 个馒头,则相当于在子节点 \(j\) 的位置累了 \(tot_j\) 个馒头,目标是获得每次取走不同位置上的两个馒头,最多可以取多少次。记 \(sum=\sum\limits_{j\in son_i} tot_j\),则易得最多有一摞馒头的个数大于 \([\frac{sum+1}{2}]\)。按馒头数由大到小,遍历每一摞馒头,并把其的馒头尽可能地放到最接近累满 \([\frac{sum+1}{2}]\) 个馒头的位置。易得,最后会获得两摞馒头,且每一层的馒头都是不同的两种(如果这层有两个馒头的话)。但是,若 \(\max\limits_{j\in son_i}\{tot_j\}>[\frac{sum+1}{2}]\),则无论如何完成这些子任务都要 \(ans_{j_0|tot_{j_0}=\max\limits_{j\in son_i}\{tot_j\}}\) 的时间;否则,只需 \([\frac{sum+1}{2}]\) 的时间完成所有的子任务。故 \(ans_i=a_i+\max([\frac{sum+1}{2}],ans_{j_0|tot_{j_0}=\max\limits_{j\in son_i}\{tot_j\}})\)\(tot_i=a_i+\sum\limits_{j\in son_i}tot_j\)
  2. 时间复杂度为 \(\mathcal O(n)\)

代码

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5;
int n,a[maxn];
ll tot[maxn],ans[maxn];
int c[maxn];
vector<int> son[maxn];
void dfs(int u)
{
    ll sum=0,toti,tmxi;
    if(son[u].size()==0)
    {
        ans[u]=tot[u]=a[u];
        return;
    }
    for(int i=0;i<son[u].size();i++)
    {
        int v=son[u][i];
        dfs(v);
        tot[u]+=tot[v];
        ans[u]=max(ans[u],ans[v]);
    }
    ans[u]=max(ans[u],(1+tot[u])>>1);
    ans[u]+=a[u],tot[u]+=a[u];
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<n;i++)
        scanf("%d",&c[i]),
        son[c[i]].push_back(i+1);
    dfs(1);
    printf("%lld",ans[1]);
    return 0;
}
posted @ 2023-07-21 09:00  shyiaw  阅读(57)  评论(0)    收藏  举报