2020牛客暑期多校训练营(第一场)题解

首先由于作者水平有限,可能只有部分的题解,且有些做法不一定标准,但是均可过题,大家见谅...

A: B-Suffix Array
这是一道结论题:首先题目是要求求一个B数组,但是这个B数组的求***有一定的问题,也就是整个字符串的B数组的一个后缀,但是它不一定满足是这个后缀的B数组,感觉可能有点绕...
然后B数组是求的和这个数最接近的pre,那么我们可以同理搞一个C数组,然后求一个与这个数最接近的suf,然后根据这个C数组做一下后缀排序,然后逆序的就是这个ans数组!
上面没有证明,这个证明可以感性理解下,感觉有一篇博客写的比较好,大概是你考虑比较Cx比Cy大,和Cx比Cy小的2种情况,然后分别发现与之对应的Bx和By的情况恰好相反,可以多举几个例子就可以发现。
同理这样的思路类似的字符串题也可以转化一下!
然后就是如果没有suf的情况,我们就设它为n,然后最后要在末尾加一个很大的数,这样保证答案的正确性...(这是一个易错点)
(另:这种做法叉姐说当且仅当字符集是2才满足条件,大家谨慎使用)
代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=100005;
int n,buc[N],c[N],sa[N],rnk[N],fir[N],sec[N];char str[N];
void print(){
    for(int i=1;i<=n;i++)printf("%d%c",sa[i]," \n"[i==n]);
    for(int i=1;i<=n;i++)printf("%d%c",rnk[i]," \n"[i==n]);
}
void suffixsort(){
    for(int i=1;i<=n;i++)buc[i]=0;
    for(int i=1;i<=n;i++)buc[c[i]]++;
    for(int i=1;i<=n;i++)buc[i]+=buc[i-1];
    for(int i=n;i>=1;i--)sa[buc[c[i]]--]=i;
    rnk[sa[1]]=1;for(int i=2;i<=n;i++)rnk[sa[i]]=rnk[sa[i-1]]+(c[sa[i]]!=c[sa[i-1]]);
    //print();
    for(int k=1;k<=n;k<<=1){
        for(int i=1;i<=n;i++)fir[i]=rnk[i],sec[i]=(i+k<=n?rnk[i+k]:0);
        for(int i=1;i<=n;i++)buc[i]=0;
        for(int i=1;i<=n;i++)buc[sec[i]]++;
        for(int i=1;i<=n;i++)buc[i]+=buc[i-1];
        for(int i=n;i;i--)rnk[buc[sec[i]]--]=i;
        for(int i=1;i<=n;i++)buc[i]=0;
        for(int i=1;i<=n;i++)buc[fir[i]]++;
        for(int i=1;i<=n;i++)buc[i]+=buc[i-1];
        for(int i=n;i>=1;i--)sa[buc[fir[rnk[i]]]--]=rnk[i];
        rnk[sa[1]]=1;for(int i=2;i<=n;i++)rnk[sa[i]]=rnk[sa[i-1]]+(fir[sa[i]]!=fir[sa[i-1]]||sec[sa[i]]!=sec[sa[i-1]]);
        //printf("k:%d\n",k);print();
    }
}
int main(){
    while(scanf("%d",&n)!=EOF){
        scanf("%s",str+1);
        for(int i=1;i<=n;i++){
            c[i]=0;
            for(int j=i+1;j<=n;j++)if(str[j]==str[i]){c[i]=j-i;break;}
            if(!c[i])c[i]=n;
        }
        n++;c[n]=n;suffixsort();
        for(int i=n-1;i>=1;i--)printf("%d%c",sa[i]," \n"[i==1]);
    }
    return 0;
}

F:Infinite String Comparision
这场的签到题,感觉没有什么好说的吧,就是大概我们长度为x和y,比较到x+y就可以了,其实是x+y-gcd(x,y)但是我也不是很会证明,考试的时候可以举一个100000和99999的极限例子就可以发现这个规律
作者在比赛的时候为了保险比到了3*max(x,y),这也是一种不错的考场做法...
代码这里可以有很多写法,实现起来很简单,就不给了/xk
H:Minimum-cost Flow
这题考察了一些有关于网络流的流量一些变形和转化
首先是我们可以发现这里的流量和费油可以同时乘上一个常数c,然后最后计算的时候我们只需要把它再除回来就可以了!
然后我们可以搞一个1作为稳定的流量,针对不同的流量我们只需要除一个常数1/c即可!
注意,这个ans函数在坐标系里是凸函数,因此我们增广的时候用一个vector<int>span,记录一下增广的过程的费用,然后我们只需要在询问的时候遍历这个vector然后卡到最优即可,由于图的大小较小,所以这个vector的大小不会很大,我们就O(size)完成了一次查询!
注意一种NaN的情况,就是无论如何都增广不到指定ans的一种可能,要特判一下!
代码:</int>

#include<bits/stdc++.h>
#define LL long long
#define pb push_back
using namespace std;
const int N=105,M=205,Inf=2e9;
int n,m,q,fst[N],tote,s,t,dis[N],mn[N],pre[N],from[N];bool inq[N];LL flow,cost;
struct Edge{int to,f,w,nxt;}e[M];
vector<LL>span;
inline void adde(int u,int v,int f,int w){
    e[++tote]=(Edge){v,f,w,fst[u]};fst[u]=tote;
    e[++tote]=(Edge){u,0,-w,fst[v]};fst[v]=tote;
}
inline bool EK(){
    queue<int>que;while(!que.empty())que.pop();
    for(int i=1;i<=n;i++)dis[i]=Inf,inq[i]=false;
    que.push(s),inq[s]=true,dis[s]=0,mn[s]=Inf;
    while(!que.empty()){
        int u=que.front();que.pop();inq[u]=false;
        for(int i=fst[u];i!=-1;i=e[i].nxt){
            int v=e[i].to,f=e[i].f,w=e[i].w;
            if(f>0&&dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;mn[v]=min(mn[u],f);pre[v]=i;from[v]=u;
                if(!inq[v])inq[v]=true,que.push(v);
            }
        }
    }
    return dis[t]<Inf;
}
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        for(int i=1;i<=n;i++)fst[i]=-1;tote=1;
        s=1;t=n;flow=cost=0;span.clear();
        for(int i=1,u,v,w;i<=m;i++)scanf("%d%d%d",&u,&v,&w),adde(u,v,1,w);
        while(EK()){
            for(int i=t;i!=s;i=from[i])e[pre[i]].f-=mn[t],e[pre[i]^1].f+=mn[t];
            flow+=(LL)mn[t];cost+=(LL)dis[t]*mn[t];span.pb(dis[t]);
        }
        scanf("%d",&q);
        while(q--){
            LL u,v;scanf("%lld%lld",&u,&v);//u/v*v/u
            if(flow*u<v)puts("NaN");
            else{
                LL ff=v,cc=u,ans=0;
                for(auto x:span)if(ff>=u)ff-=u,ans+=u*x;else{ans+=ff*x;break;}
                LL g=__gcd(ans,v);printf("%lld/%lld\n",ans/g,v/g); 
            }
        }
    }
    return 0;
}

I:1 or 2
这题的正解好像是什么带花树,很神仙,作者不会
于是这里有两种优秀的做法,时间在目前配置的较水的数据比标算快
1 随机化,也就是叉姐说的概率算法,可以每次考虑还不满足条件的情况,然后用一个vector记录一个点的边的编号和另外一个点,然后我们把不满足条件的一一调整,这里可能是一种比较奇怪的随机化,类似于相对于一个模板我们不断类似于退火的思想慢慢靠近,然后最后判断是否满足条件,中间到底是先调整哪边我们要用随机的思想,这样可以较好的利用最优性的概率,然后达到算法的稳定性!
2 dfs爆搜,这个算法需要改变一下搜索的方式,然后我们加一些剪枝就可以通过,比较玄学!可以见代码:

#include<bits/stdc++.h>
#define LL long long
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int N=105;
int n,m,d[N],id[N];bool usd[N];vector<pii>e[N];
bool dfs(int dep){
    if(dep>n)return true;
    int u=id[dep];
    if(!d[u])return dfs(dep+1);
    for(auto edg:e[u]){
        int v=edg.first,num=edg.second;
        if(!d[v]||usd[num])continue;
        d[u]--,d[v]--,usd[num]=true;
        if(dfs(dep))return true;
        d[u]++,d[v]++,usd[num]=false;
    }
    return false;
}
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        for(int i=1;i<=n;i++)scanf("%d",&d[i]),id[i]=i,e[i].clear();
        for(int i=1,u,v;i<=m;i++)scanf("%d%d",&u,&v),e[u].pb(pii(v,i)),e[v].pb(pii(u,i)),usd[i]=false;
        sort(id+1,id+n+1,[&](int u,int v){return e[u].size()<e[v].size();});
        puts(dfs(1)?"Yes":"No");
    }
    return 0;
}

J:Easy Integration
积分题,作者不太会证明,考试的时候降智了,没写出来...
后来可以通过分子和分母找规律,可以得到结论:
(n!)^2/(2n+1)!
这里有一个好办法,就是通过模数和分数给出在模数下取模的形式,即ans,得到分子和分母(dreamoon神仙以前出的一道题)
给出这个找分子和分母的程序(网上的代码,不是作者写的,这里借鉴一下)
找分数的代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
void f(ll a, ll b, ll c, ll d, ll& x, ll& y){
    ll q = a / b + 1;
    if (q <= c / d){
        x = q;
        y = 1;
        return;
    }
    q--;
    a -= q * b; c -= q * d;
    f(d, c, b, a, y, x);
    x += q * y;
    return;
}
int main(){
    int t;
    scanf("%d", &t);
    while (t--){
        ll b, y, a, p, x;
        scanf("%lld%lld", &p, &x);
        f(p, x, p, x - 1, b, y);
        a = b * x - y * p;
        printf("%lld/%lld\n", a, b);
    }
    return 0;
}

作者本题的代码就不给了,很好实现一个快速幂和逆元就可以完事了...见谅

全部评论

相关推荐

不愿透露姓名的神秘牛友
11-27 10:28
点赞 评论 收藏
分享
M_bao:换个排版吧哥们,看着费劲
点赞 评论 收藏
分享
粗心的雪碧不放弃:纯学历问题,我这几个月也是一直优化自己的简历,后来发现优化到我自己都觉得牛逼的时候,发现面试数量也没有提升,真就纯学历问题
点赞 评论 收藏
分享
手撕没做出来是不是一定挂
Chrispp3:不会,写出来也不一定过
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务