【题解】牛客练习赛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
为什么三分的判定条件是 r-l>10
1 回复 分享
发布于 2020-03-13 22:18
C题可以用线性规划。高中学的那个
1 回复 分享
发布于 2020-03-13 22:25
C题可以不用三分,每次O(1)算出答案
1 回复 分享
发布于 2020-03-13 22:51
c 线性规划就可以证明三分的正确性了吧,再考虑2x+4y<=a 与3x+y<=b 的合并得出 x+y<=(a-a%2+b)/5,特判一下交点不在第一象限的情况;O(1)即可;
1 回复 分享
发布于 2020-03-13 23:19
一看就是谈爱的出题人
1 回复 分享
发布于 2020-03-13 23:54
E题线段树区间合并时间复杂度不是n的三次方吗
点赞 回复 分享
发布于 2020-03-13 22:11
C题是卡数据了吗?
点赞 回复 分享
发布于 2020-03-13 22:19
E题怎么做都比题解优吧 闵可夫斯基和n^3 贪心每次取变化量最小的n^2log😑
点赞 回复 分享
发布于 2020-03-13 22:21
F能不能提供一点点数据啊..本地随机了几个极限都很稳的过了..不知道为啥交上来就挂了/kk
点赞 回复 分享
发布于 2020-03-13 22:34
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
C题做法怎么证明是对的
点赞 回复 分享
发布于 2020-03-13 22:40
C题线性规划可以做吗,坑点在哪呢
点赞 回复 分享
发布于 2020-03-13 23:05
E题裸dp每次是n^3,但是可以3分优化成n^2logn,只过了83.33%我真的不知道是我哪里写错了还是压根三分就是错的
点赞 回复 分享
发布于 2020-03-14 11:04
c直接O(1)回答每次询问,又快又好写
点赞 回复 分享
发布于 2020-03-14 13:44
D题是漏了7吗
点赞 回复 分享
发布于 2020-03-14 16:01

相关推荐

6 4 评论
分享
牛客网
牛客企业服务