我是靠谱客的博主 殷勤河马,最近开发中收集的这篇文章主要介绍2020牛客暑期多校训练营(第一场)AFIHJ,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

总结
这次比赛题面很难理解,还是英语太菜,先开的I,但是因为题意表述不清,然后题意讨论了好久,然后发现F有人过了,就去写F,结果F题意理解错了导致WA了两发,及时发现改正,然后开的J,手推了一会儿,然后交给lyx去写,因为取模的问题WA了一发,然后又回来看I题,决定试一试题意,结果到最后题意理解还是错的。

牛客多校一报告

  • A
    • 题意
    • 思路
    • 代码
  • F
    • 题意
    • 思路
    • 代码
  • I
    • 题意
    • 思路
    • 代码
  • H
    • 题意
    • 思路
    • 代码
  • J
    • 题意
    • 思路
    • 代码

A

题意

给定一个字符串,根据题目要求将每个后缀的b函数进行排序,从小到大输出对应后缀的起始位置。

思路

对B序列建后缀数组。定义c[i]=j-i,j为索引i后面最近的索引使得s[i]==s[j],即c[i]的值是i后面离“i指针”对应的字母最近的与s[i]同字符的距离。c数组后缀排序就是实际字符串b函数从大到小的排序。由此用后缀数组对c数组求sa数组即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false),cin.tie(0);
#define ll long long
#define inf 0x3f3f3f3f
const int N=1e5+5;
//set<string>b;
//set<string>::iterator it;
int n,m,sa[N],x[N],y[N],p[N];
char s[N];
void quicksort()
{
	int i;
	for(i=0;i<m;i++)
	{
		p[i]=0;
	}
	for(i=0;i<n;i++)
	{
		p[x[i]]++;
	}
	for(i=1;i<m;i++)
	{
		p[i]+=p[i-1];
	}
	for(i=n-1;i>=0;i--)
	{
		sa[--p[x[y[i]]]]=y[i];
	}
}
void get_sa()
{
	quicksort();
	int i,j,t;
	for(j=1,t=1;t<n;j<<=1,m=t)
	{
		t=0;
		for(i=n-j;i<n;i++)
		{
			y[t++]=i;
		}
		for(i=0;i<n;i++)
		{
			if(sa[i]>=j) y[t++]=sa[i]-j;
		}
		quicksort();
		for(i=0;i<n;i++)
		{
			y[i]=x[i];
		}
		x[sa[0]]=0,t=1;
		for(i=1;i<n;i++)
		{
			if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j])
				x[sa[i]]=t-1;
			else
				x[sa[i]]=t++;
		}
	}
}
int main()
{
    IO;
    int a,b,i,j;
    while(cin>>n)
    {
    	cin>>s;
		a=n;b=n;
    	for(i=n-1;i>=0;i--)
    	{
    		y[i]=i;
    		if(s[i]=='a')
    		{
    			if(a==n) x[i]=1;
    			else x[i]=n-a+i+1;
    			a=i;
			}
			else
			{
				if(b==n) x[i]=1;
				else x[i]=n-b+i+1;
				b=i;
			}
		}
		x[n]=0;y[n]=n;
		n++;
		m=n+1;
		get_sa();
		for(i=1;i<n-1;i++)
		{
			cout<<sa[i]+1<<" ";
		}
		cout<<sa[n-1]+1<<endl;
	} 
    return 0; 
}

F

题意

给你两个字符串,比较这两个字符串各自重复无限次以后的字典序。

思路

将两个字符串重复到这两个字符串长度中较大的那个的两倍的长度,就可以比较了。在重复过程中若字符不相等可直接比较出答案。

代码

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0), cin.tie(0)
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const int inf = ~0u >> 1;
typedef pair<int,int> P;
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n) - 1; i >= a; --i)
int main() {
    IO;
    string a, b;
    while (cin >> a >> b) {
        if (a == b) {
            cout << "=" << endl;
        }
        else {
            string aa, bb;
            int la = a.length(), lb = b.length();
            int cnt = max(la,lb)*2;
            bool flag=true;
            REP(i, 0, cnt) {
                if(a[i % la]>b[i % lb]) {
                    cout << ">" << endl;
                    flag=false;
                    break;
                }
                else if(a[i % la]<b[i % lb]) {
                    cout << "<" << endl;
                    flag=false;
                    break;
                }
            }
            if (flag==true)
            {
                cout << "=" << endl;
            }
             
        }
    }
     
    return 0;
}

I

题意

给你一个n个点m条边的无向图,通过删除一些边使每个点的度数满足di,若存在则输出Yes,否则输出No。

思路

通过拆点来跑一般图的最大匹配,将度数为2的点进行拆点,拆完的点连向对应的x,y点。如果达到完美匹配,这条边的另一头必定匹配着另一个点的一个度,表示拆点原点相连,这样一条边的匹配是合乎要求的。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+5;
deque<int>q;
bool G[N][N],g[N][N],inque[N],inblossom[N],vis[N];
int match[N],pre[N],base[N];
int n,m,t,u,v,d,id;
vector<int>V[N];
struct node{
    int u,v;
}E[N];
int Find(int u,int v){
    bool inpath[N]={false};
    while(1){
        u=base[u];
        inpath[u]=true;
        if(match[u]==-1)break;
        u=pre[match[u]];
    }
    while(1){
        v=base[v];
        if(inpath[v])return v;
        v=pre[match[v]];
    }
}

void reset(int u,int anc){
    while(u!=anc){
        int v=match[u];
        inblossom[base[u]]=1;
        inblossom[base[v]]=1;
        v=pre[v];
        if(base[v]!=anc)pre[v]=match[u];
        u=v;
    }
}

void contract(int u,int v,int n){
    int anc=Find(u,v);
    memset(inblossom,0,sizeof(inblossom));
    reset(u,anc);reset(v,anc);
    if(base[u]!=anc)pre[u]=v;
    if(base[v]!=anc)pre[v]=u;
    for(int i=1;i<=n;i++)
        if(inblossom[base[i]]){
            base[i]=anc;
            if(!inque[i]){
                q.push_back(i);
                inque[i]=1;
            }
        }
}

bool dfs(int s,int n){
    for(int i=0;i<=n;i++)pre[i]=-1,inque[i]=0,base[i]=i;
    q.clear();q.push_back(s);inque[s]=1;
    while(!q.empty()){
        int u=q.front();q.pop_front();
        for(int v=1;v<=n;v++){
            if(g[u][v]&&base[v]!=base[u]&&match[u]!=v){
                if(v==s||(match[v]!=-1&&pre[match[v]]!=-1))contract(u,v,n);
                else if(pre[v]==-1){
                    pre[v]=u;
                    if(match[v]!=-1)q.push_back(match[v]),inque[match[v]]=1;
                    else{
                        u=v;
                        while(u!=-1){
                            v=pre[u];
                            int w=match[v];
                            match[u]=v;
                            match[v]=u;
                            u=w;
                        }
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

void calculate()
{
    int ans=0;
    memset(match,-1,sizeof(match));
    for(int i=1;i<=id-1;i++)
        if(match[i]==-1&&dfs(i,id-1)) ans++;
    if(ans*2==id-1) printf("Yesn");
    else printf("Non");
}

void init(){
    id=1;
    memset(G,0,sizeof(G));
    memset(g,0,sizeof(g));
    for(int i=1;i<=n;i++) V[i].clear();
}

void build(){
    for(int i=1;i<=m;i++){
        for(auto k:V[E[i].u]) g[k][id]=g[id][k]=1;
        id++;
        for(auto k:V[E[i].v]) g[k][id]=g[id][k]=1;
        g[id][id-1]=g[id-1][id]=1;
        id++;
    }
}

int main(){
    while(~ scanf("%d%d",&n,&m)){
        init();
        for(int i=1;i<=n;i++){
            scanf("%d",&d);
            while(d--) V[i].push_back(id++);
        }
        for(int i=1;i<=m;i++){
            scanf("%d%d",&u,&v);
            G[u][v]=G[v][u]=1;
            E[i].u=u,E[i].v=v;
        }
        build();
        calculate();
    }
}

H

题意

给你一个n个点,m条边的有向图,并给出每条边的费用。然后进行q次询问,每次询问给出每条边的容量u/v,并且所有边的容量相等。 对于每次询问,你需要输出总流量为1时,从点1到点n的最小费用(分数表示)。

思路

比赛时候看了这个题,题意没看很懂,感觉是网络流的东西,但是如果每次询问都跑一遍显然是会T掉的,赛后看了别人的思路,发现是最小费用最大流的题,还需要预处理。
对于每次询问,总流量为1,每条边容量为u/v。考虑缩放,同时乘以v,则总流量为v,每条边容量为u,这时算出来的总费用除以v即为答案。 我们可以在询问之前,预处理得到所有增广路的费用,每次进行一次SPFA算法后,就能得到一条增广路的费用,将其记录于path数组中(按增广顺序能保证每条增广路费用是升序的),并求其前缀和,方便后续询问的处理。
查询之前,先预处理得到:
(1)path[a],下标从0开始,表示单位容量(流量跑满,流量=容量)时第a+1条增广路费用。
(2)sum[a],下标从1开始,表示单位容量(流量跑满,流量=容量)时前a条增广路总费用。
接下来处理每次询问。
假设v=a*u+b(b<u),即前a条增广路流量都为u,第a+1条增广路流量达不到u,流量为b。
由于sum和path是之前预处理得到的单位容量情况下的费用,所以sum[a]要乘以u得到流量为u时前a条增广路总费用,path[a]要乘以b得到流量为b时第a+1条增广路费用,则ans=(sum[a]*u+path[a]*b)/v。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1010,M=1010,inf=(1ll<<63)-1;
ll n,m,s,t,cnt,mincost,head[N],dis[N],vis[N],cur[N],sum[M];
vector<ll>path;
struct edge
{
    ll to,next,cap,cost;//cap是容量,cost是单位流量的花费
}e[M<<1];
void add(ll x,ll y,ll z,ll c)
{
    e[cnt].to=y;
    e[cnt].cap=z;
    e[cnt].cost=c;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
void add_edge(ll x,ll y,ll z,ll c)
{
    add(x,y,z,c);
    add(y,x,0,-c);//反向边容量为0,单位流量的花费为-c
}
bool spfa()
{
    queue<int>q;
    for(ll i=1;i<=n;i++)
        dis[i]=inf;
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    vis[s]=1;//入队标记
    q.push(s);
    while(!q.empty())
    {
        ll u=q.front();q.pop();
        vis[u]=0;//出队标记
        for(ll i=head[u];i!=-1;i=e[i].next)
        {
            ll v=e[i].to;
            if(e[i].cap>0&&dis[u]+e[i].cost<dis[v])
            {
                dis[v]=dis[u]+e[i].cost;
                if(!vis[v])
                {
                    vis[v]=1;//入队标记
                    q.push(v);
                }
            }
        }
    }//队列为空时 vis也全部被置为0
    if(dis[t]!=inf)return 1;
    return 0;
}
ll dfs(ll u,ll flow)
{
    vis[u]=1;//注意用vis标记走过的点,防止死循环
    if(u==t)return flow;//到达汇点,返回这条增广路上的最小流量
    for(ll& i=cur[u];i!=-1;i=e[i].next)
    {
        ll v=e[i].to;
        if(e[i].cap>0&&dis[v]==dis[u]+e[i].cost&&!vis[v])
        {
            ll di=dfs(v,min(flow,e[i].cap));//min(flow,e[i].w)表示起点到v的最小流量
            if(di>0)//防止dfs结果return 0的情况,如果di=0会死循环
            {
                e[i].cap-=di;
                e[i^1].cap+=di;//反向边加上di
                mincost+=di*e[i].cost;
                return di;//di表示整条增广路上的最小流量,回溯的时候一直向上返回,返回的di是不变的
            }
        }
    }
    vis[u]=0;//还原标记的点
    return 0;//找不到增广路,到不了汇点
}
ll dinic()
{
    ll maxflow=0;
    path.clear();
    while(spfa())//先spfa进行"探路"并分层,分层后进行多次dfs
    {
        path.push_back(dis[t]); // 每次spfa后得到一条增广路的费用
        memcpy(cur,head,sizeof(head));
        while(ll d=dfs(s,inf))//while循环的意义:只要dfs不为0,就一直dfs,直到找不到增广路
            maxflow+=d;
    }
    return maxflow;
}
// 以上都是dinic算法的模板,只是增加了path数组,用来在每次spfa后记录dis[t]。

int main()
{
    ios::sync_with_stdio(false);
    ll x,y,c,q,u,v;
    while(cin>>n>>m)
    {
        s=1,t=n;
        memset(head,-1,sizeof(head));
        cnt=0;
        mincost=0;
        for(ll i=1;i<=m;i++)
        {
            cin>>x>>y>>c;  // 点、边、费用
            add_edge(x,y,1,c); // 将每条边容量置为1
        }
        dinic(); // 跑费用流,得到path数组记录在单位容量的情况下,每条增广路上的费用(从小到大)
        ll pnum=path.size(); // 增广路个数
        for(ll i=0;i<pnum;i++)
            sum[i+1]=sum[i]+path[i]; // path[i]是第i+1条增广路费用
        // 查询之前,先预处理得到:
        // sum[a],表示单位容量(流量跑满,流量=容量)时前a条增广路总费用
        // path[a],表示单位容量(流量跑满,流量=容量)时第a+1条增广路费用
        cin>>q;
        while(q--)
        {
            cin>>u>>v;
            // 每条增广路容量由u/v扩成u,总流量由1扩成v,最后费用除以v即可
            if(u*pnum<v)printf("NaNn"); // 容量<总流量
            else
            {
                // 假设:v=a*u+b(b<u),即前a条增广路流量都为u,第a+1条增广路流量达不到u,流量为b
                ll a=v/u;
                ll b=v%u;
                // 由于sum和path是之前预处理得到的单位容量情况下的费用
                // 所以sum[a]要乘以u得到流量为u时前a条增广路总费用,
                // path[a]要乘以b得到流量为b时第a+1条增广路费用
                ll ans=sum[a]*u+path[a]*b;
                ll k=__gcd(ans,v);
                ans/=k;
                v/=k;
                printf("%lld/%lldn",ans,v);
            }
        }
    }
    return 0;
}


J

题意

题目很简洁明了,求一个定积分结果要以p*q^-1的形式(mod998244353)

思路

看到题目第一反应求逆元,然后就是求解定积分了。数学基础不牢我们手算了n=1,2,3,4,5,6,7的结果,然后找出a[i]=a[i-1]2i/(4*(2*i+1))的规律,最后用费马小定理求逆元并记得用ll存数即可。

代码

#include<iostream>
#include<cmath>
#include<stack>
#include<vector>
#include<cstring>
#include<algorithm>
#include<set>
#include<queue>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <complex>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cassert>
using namespace std;
typedef long long ll;
const ll inf =1e17;
#define scn(a) scanf("%d",&a)
#define scd(a) scanf("%lf",&a)
#define scl(a) scanf("%lld",&a)
#define ptf(a) printf("%dn",a)
#define mes(a,b) memset(a,b,sizeof(a))
#define fon(s,n) for(int i=s;i<=n;i++)
#define range(i,a,b) for(int i=a;i<=b;++i)
#define rerange(i,a,b) for(int i=a;i>=b;--i)
//#define N 100010
const ll p =998244353;
const int S=20;
ll gcd(ll a,ll b) { return b>0 ? gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a*b/gcd(a,b);}
ll q_pow(ll a,ll b,ll mod)
{
ll ans=1,res=a;
while(b){
if(b&1) ans=ans*res%mod;
res=res*res%mod;
b>>=1;
}
return ans%mod;
}
ll a[1000005];

void init(){
for(ll i=2;i<=1000000;i++){
a[i]=((a[i-1]%p)*(4*(2*i+1)%p))%p;
a[i]*=q_pow(2*i,p-2,p);
a[i]%=p;
}
}
int main()  
{  
a[1]=6;init();
ll n;
while(~scl(n)){
ll ans=q_pow(a[n],p-2,p);
cout<<ans<<endl;
}
    return 0;  
}

最后

以上就是殷勤河马为你收集整理的2020牛客暑期多校训练营(第一场)AFIHJ的全部内容,希望文章能够帮你解决2020牛客暑期多校训练营(第一场)AFIHJ所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(36)

评论列表共有 0 条评论

立即
投稿
返回
顶部