【题解】牛客练习赛59

A:小乔和小灰灰

表示字符串遍历到位置,与字符串匹配的最大长度。
表示字符串遍历到位置,与字符串匹配的最大长度。
则转移为,
最后判断最大长度是否与两个字符串的长度相等即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n;
char s[N];
string a="XiaoQiao";
string b="XiaoHuiHui";
int x,y;
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    assert(n>=1&&n<=1000);
    for(int i=1;i<=n;i++)
        assert(s[i]>='a'&&s[i]<='z'||s[i]>='A'&&s[i]<='Z');
    for(int i=1;i<=n;i++)
    {
        if(s[i]==a[x]) x++;
        if(s[i]==b[y]) y++;
    }
    if(x==a.size()&&y==b.size()) printf("Happy\n");
    else printf("emm\n");
}

B:牛能和小镇

题目显然是要我们求一颗最小生成树。我们把距离公式交换一下顺序:

,那么两点之间的距离为
我们把所有点按排一下序,相邻两点之间连一条边即可得到最小生成树,时间复杂度

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n;
ll a[N];
int main()
{
    scanf("%d",&n);
    assert(n>=1&&n<=100000);
    for(int i=1;i<=n;i++)
    {
        ll x,y;scanf("%lld%lld",&x,&y);
        assert(x>=1&&x<=100000);
        assert(y>=1&&y<=100000);
        a[i]=x*x*y+y*y*(y-2*x);
    }
    sort(a+1,a+1+n);
    ll ans=0;
    for(int i=1;i<n;i++) ans+=a[i+1]-a[i];
    printf("%lld\n",ans);
}

C:装备合成

假设第一种装备合成了件,那么第二种装备可以合成件。朴素的做法是枚举,然后取最优答案,但是会
于是考虑升级以上做法:
1.做一个假设,假设上述做法存在单调极值性,那么我们可以通过简单的三分做出来。
2.随机一些数据打表,打表后发现果然存在单调极值性,那么我们可以通过简单的三分做出来。
综上所述,我们可以对进行三分,单组数据的时间复杂度,总的时间复杂度

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int t;scanf("%d",&t);
    assert(t>=1&&t<=10000);
    while(t--)
    {
        int x,y;scanf("%d%d",&x,&y);
        assert(x>=1&&x<=1000000000);
        assert(y>=1&&y<=1000000000);
        int l=0,r=min(x/2,y/3);
        while(r-l>10)
        {
            int m1=l+(r-l)/3,m2=r-(r-l)/3;
            if(m1+min((x-2*m1)/4,y-3*m1)>m2+min((x-2*m2)/4,y-3*m2))
                r=m2;
            else l=m1;
        }
        int ans=0;
        for(int i=l;i<=r;i++)
            ans=max(ans,i+min((x-2*i)/4,y-3*i));
        printf("%d\n",ans);
    }
}

D:取石子游戏

首先,当是一定是必败态。
那么可以转移到的状态是,即为必胜态。
只能转移到的状态为,即为必败态。
能转移到的状态为,为必胜态。
......
根据上述依次类推,可以发现必胜态和必败态是一段一段区间这样出现的,并且长度会不断增长,增长的长度与的幂次有关,那么我们记录每一段的头和尾打表,对于每个查询,二分一下在哪一段区间,根据状态输出答案即可。时间复杂度

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=105;
int tot;
ll s[N],e[N];
int main()
{
    s[++tot]=1;e[tot]=1;
    s[++tot]=2;e[tot]=3;
    while(true)
    {
        tot++;
        s[tot]=e[tot-1]+1;
        if(tot&1)
            e[tot]=e[tot-1]*2;
        else
            e[tot]=e[tot-1]*2+1;
        if(e[tot]>1e18) break;
    }
    int t;scanf("%d",&t);
    assert(t>=1&&t<=100000);
    while(t--)
    {
        ll n;scanf("%lld",&n);
        assert(n>=1&&n<=(ll)1000000000000000000);
        int pos=upper_bound(s+1,s+1+tot,n)-s-1;
        if(pos&1) printf("XiaoQiao\n");
        else printf("XiaoHuiHui\n");
    }
}

E:石子搬运

首先,让我们看看问题不修改的话我们怎么做。
表示第堆石子搬运次需要的最小代价。对于单堆石子,最优的策略是把个石子均分在次搬运上。可以证明这是最优的策略:
假设这样产生的花费为,而每次搬运的个数都为,我们把某次搬运的个数,加到另一次搬运上,重新计算的花费为,对于任意的,一定有,那么花费增加了。
再考虑两堆石子,我们知道,另表示两堆搬运次全部搬完的代码,有
如果没有修改,我们从前往后遍历进行得出答案,这样单次处理的时间复杂度为。如果处理次,因为同阶,时间复杂度为,但复杂度太高会得到
注意到本题每次仅修改一个点,那么是否可以把稍做修改,使得每次修改后可以快速得出答案呢?这显然是可以的,考虑堆石子,我们先合并的答案,再合并的答案,再将的答案合并,这不影响最终的结果。
这样,我们可以采用分治的思想来进行(类似于线段树),因为最多个结点,所以初始化的时间复杂度为。,但对于后面的每次修改,就相当于线段树单点修改,单点修改的时间复杂度为,总的时间复杂度,发现这样复杂度其实也挺大的,但别忘了还有一个小技巧优化,利用优化后时间跑不满,足以通过这道题。

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N=405;
int n,m,q,sum,a[N],len[N<<2];
ll dp[N<<2][N];
void update(int k)
{
    memset(dp[k],inf,sizeof(dp[k]));
    int ls=k<<1,rs=k<<1|1;
    for(int i=len[ls];i<=m;i++)
        for(int j=len[rs];i+j<=m;j++)
    {
        dp[k][i+j]=min(dp[k][i+j],dp[ls][i]+dp[rs][j]);
    }
}
void build(int l,int r,int k)
{
    len[k]=r-l+1;
    if(l==r)
    {
        memset(dp[k],inf,sizeof(dp[k]));
        for(int i=1;i<=min(a[l],m);i++)
        {
            dp[k][i]=a[l]/i;
            dp[k][i]=a[l]%i*(dp[k][i]+1)*(dp[k][i]+1)+(i-a[l]%i)*dp[k][i]*dp[k][i];
        }
        return;
    }
    int m=l+r>>1;
    build(l,m,k<<1);build(m+1,r,k<<1|1);
    update(k);
}
void fix(int l,int r,int k,int x)
{
    if(l==r)
    {
        memset(dp[k],inf,sizeof(dp[k]));
        for(int i=1;i<=min(a[l],m);i++)
        {
            dp[k][i]=a[l]/i;
            dp[k][i]=a[l]%i*(dp[k][i]+1)*(dp[k][i]+1)+(i-a[l]%i)*dp[k][i]*dp[k][i];
        }
        return;
    }
    int m=l+r>>1;
    if(x<=m) fix(l,m,k<<1,x);
    else fix(m+1,r,k<<1|1,x);
    update(k);
}
int main()
{
    scanf("%d%d",&n,&m);
    assert(n>=1&&n<=400);
    assert(m>=n&&m<=400);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),assert(a[i]>=1&&a[i]<=100000),sum+=a[i];
    build(1,n,1);
    scanf("%d",&q);
    while(q--)
    {
        int x,v;scanf("%d%d",&x,&v);
        assert(x>=1&&x<=n);assert(v>=1&&v<=100000);
        sum-=a[x];
        sum+=v;
        a[x]=v;
        fix(1,n,1,x);
        printf("%lld\n",dp[1][min(sum,m)]);
    }
}

F:小松鼠吃松果

对于第个松果,其落地的时间为,那么如果小松鼠吃了第个松果还能吃第个松果,当且仅当松果的落地时间大于等于松果且他们的落地时间之差大于等于其横向距离。接下来,我们默认为。将所有松果按落地时间排序,我们可以得到一个简单的,对于排序后的松果,有对于所有的满足,这样的时间复杂度为,我们会得到
考虑把条件拆分一下

如果


注意到此时一定满足

如果


注意到此时一定满足

。对于同时满足,就是可以转移的,这变成了一个二维偏序关系,于是可以排序后用树状数组维护,时间复杂度

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
struct node
{
    int t,p,w;
    bool operator<(const node&o)const
    {
        return t==o.t?p<o.p:t<o.t;
    }
}a[N];
int n,m,num,pos[N],b[N],hs[N];
ll t[N];
ll query(int x)
{
    ll ans=0;
    while(x)
    {
        ans=max(ans,t[x]);
        x-=x&-x;
    }
    return ans;
}
void add(int x,ll v)
{
    while(x<=n)
        t[x]=max(t[x],v),x+=x&-x;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d",&pos[i]),hs[i]=pos[i];
    for(int i=1;i<=m;i++) scanf("%d",&b[i]);
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&a[i].t,&a[i].p,&a[i].w),a[i].t+=b[a[i].p];
    for(int i=1;i<=n;i++)
    {
        int x=a[i].t-pos[a[i].p],y=a[i].t+pos[a[i].p];
        a[i].t=x;
        a[i].p=y;
        hs[i]=y;
    }
    sort(hs+1,hs+1+n);
    ll ans=0;
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++)
    {
        int pos=lower_bound(hs+1,hs+1+n,a[i].p)-hs;
        ll dp=query(pos)+a[i].w;
        ans=max(ans,dp);
        add(pos,dp);
    }
    printf("%lld\n",ans);
}
全部评论
F 真不用这么复杂,从一点到另一个点要满足的条件可以定义一个二维偏序的关系,那么就是求一个权值最大的二维偏序集,也就是求带权 LIS ,复杂度只有一个 log 还好写。。。🙄🙄🙄
5 回复 分享
发布于 2020-03-14 08:30
一看就是谈爱的出题人
1 回复 分享
发布于 2020-03-13 23:54
c 线性规划就可以证明三分的正确性了吧,再考虑2x+4y<=a 与3x+y<=b 的合并得出 x+y<=(a-a%2+b)/5,特判一下交点不在第一象限的情况;O(1)即可;
1 回复 分享
发布于 2020-03-13 23:19
C题可以不用三分,每次O(1)算出答案
1 回复 分享
发布于 2020-03-13 22:51
C题可以用线性规划。高中学的那个
1 回复 分享
发布于 2020-03-13 22:25
为什么三分的判定条件是 r-l>10
1 回复 分享
发布于 2020-03-13 22:18
D题是漏了7吗
点赞 回复 分享
发布于 2020-03-14 16:01
c直接O(1)回答每次询问,又快又好写
点赞 回复 分享
发布于 2020-03-14 13:44
E题裸dp每次是n^3,但是可以3分优化成n^2logn,只过了83.33%我真的不知道是我哪里写错了还是压根三分就是错的
点赞 回复 分享
发布于 2020-03-14 11:04
C题线性规划可以做吗,坑点在哪呢
点赞 回复 分享
发布于 2020-03-13 23:05
C题做法怎么证明是对的
点赞 回复 分享
发布于 2020-03-13 22:40
C题三分这样写会什么会wa 不加这一句可以过     "else if(lres==rres){l=lm;r=rm;}" ```cpp #include<bits/stdc++.h> using namespace std; int x,y; int check(int l){     int xx=x-l*2,yy=y-l*3;     if(xx<0||yy<0) return min(x/2,y/3);     int r=min(xx/4,yy);     return l+r; } int main(){     int T;     cin>>T;     while(T--){         cin>>x>>y;         int l=0,r=min(x/2,y/3),ans=r;         //cout<<r<<endl;         for(int i=1;i<=1000;++i){             int lm=(l*2+r)/3,rm=(l+r*2)/3;             int lres=check(lm),rres=check(rm);             //cout<<lm<<" :"<<lres<<"  "<<rm<<":"<<rres<<endl;             if(lres>rres){                 r=rm;             }             else if(lres==rres){                 l=lm;r=rm;             }             else {                 l=lm;             }             ans=max(ans,max(lres,rres));         }         cout<<ans<<endl;     }        return 0; } ```
点赞 回复 分享
发布于 2020-03-13 22:35
F能不能提供一点点数据啊..本地随机了几个极限都很稳的过了..不知道为啥交上来就挂了/kk
点赞 回复 分享
发布于 2020-03-13 22:34
E题怎么做都比题解优吧 闵可夫斯基和n^3 贪心每次取变化量最小的n^2log😑
点赞 回复 分享
发布于 2020-03-13 22:21
C题是卡数据了吗?
点赞 回复 分享
发布于 2020-03-13 22:19
E题线段树区间合并时间复杂度不是n的三次方吗
点赞 回复 分享
发布于 2020-03-13 22:11

相关推荐

头像
10-13 18:10
已编辑
东南大学 C++
。收拾收拾心情下一家吧————————————————10.12更新上面不知道怎么的,每次在手机上编辑都会只有最后一行才会显示。原本不想写凉经的,太伤感情了,但过了一天想了想,凉经的拿起来好好整理,就像象棋一样,你进步最快的时候不是你赢棋的时候,而是在输棋的时候。那废话不多说,就做个复盘吧。一面:1,经典自我介绍2,项目盘问,没啥好说的,感觉问的不是很多3,八股问的比较奇怪,他会深挖性地问一些,比如,我知道MMU,那你知不知道QMMU(记得是这个,总之就是MMU前面加一个字母)4,知不知道slab内存分配器-&gt;这个我清楚5,知不知道排序算法,排序算法一般怎么用6,写一道力扣的,最长回文子串反问:1,工作内容2,工作强度3,关于友商的问题-&gt;后面这个问题问HR去了,和中兴有关,数通这个行业和友商相关的不要提,这个行业和别的行业不同,别的行业干同一行的都是竞争关系,数通这个行业的不同企业的关系比较微妙。特别细节的问题我确实不知道,但一面没挂我。接下来是我被挂的二面,先说说我挂在哪里,技术性问题我应该没啥问题,主要是一些解决问题思路上的回答,一方面是这方面我准备的不多,另一方面是这个面试写的是“专业面试二面”,但是感觉问的问题都是一些主管面/综合面才会问的问题,就是不问技术问方法论。我以前形成的思维定式就是专业面会就是会,不会就直说不会,但事实上如果问到方法论性质的问题的话得扯一下皮,不能按照上面这个模式。刚到位置上就看到面试官叹了一口气,有一些不详的预感。我是下午1点45左右面的。1,经典自我介绍2,你是怎么完成这个项目的,分成几个步骤。我大致说了一下。你有没有觉得你的步骤里面缺了一些什么,(这里已经在引导我往他想的那个方向走了),比如你一个人的能力永远是不够的,,,我们平时会有一些组内的会议来沟通我们的所思所想。。。。3,你在项目中遇到的最困难的地方在什么方面4,说一下你知道的TCP/IP协议网络模型中的网络层有关的协议......5,接着4问,你觉得现在的socket有什么样的缺点,有什么样的优化方向?6,中间手撕了一道很简单的快慢指针的问题。大概是在链表的倒数第N个位置插入一个节点。————————————————————————————————————10.13晚更新补充一下一面说的一些奇怪的概念:1,提到了RPC2,提到了fu(第四声)拷贝,我当时说我只知道零拷贝,知道mmap,然后他说mmap是其中的一种方式,然后他问我知不知道DPDK,我说不知道,他说这个是一个高性能的拷贝方式3,MMU这个前面加了一个什么字母我这里没记,别问我了4,后面还提到了LTU,VFIO,孩子真的不会。
走呀走:华子二面可能会有场景题的,是有些开放性的问题了
点赞 评论 收藏
分享
评论
6
4
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务