(牛客2021)第一届河北工业大学程序设计竞赛(部分题解)

Posted CCSU_Cola

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(牛客2021)第一届河北工业大学程序设计竞赛(部分题解)相关的知识,希望对你有一定的参考价值。

A.WELCOME!

直接输出

#include<bits/stdc++.h>
using namespace std;
int main(){
    printf("Welcome to The First Programming Competition of Hebei University of technology!");
    return 0;
}

B.POOLING

题意: 给出n * m大小的图,求出该图中每个大小为k * k的子图中的最大的数是多少。

思路: 因为n和m很小,所以可以直接暴力写。

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int p[60][60],pp[60][60];
int get(int x,int y){
    int ans=-1;
    for(int i=x;i<x+k;i++){
        for(int j=y;j<y+k;j++){
            ans=max(ans,p[i][j]);
        }
    }
    return ans;
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&p[i][j]);
        }
    }
    printf("%d %d\\n",n-k+1,m-k+1);
    for(int i=1;i<=n-k+1;i++){
        for(int j=1;j<=m-k+1;j++){
            printf("%d ",get(i,j));
        }
        printf("\\n");
    }
    return 0;
}

C.标枪游戏

**题意: ** 一人投注一次标枪,当前投掷标枪的分数为1+(场上投掷距离小于等于当前标枪投掷距离的标枪数量) - (场上投掷距离大于当前标枪投掷距离的标枪数量),求出最后两人得分,根据分数输出结果。

思路:根据分数计算公式知道每次需要算出前面有多少个大于自己的数和小于等于自己的数,于是我们可以使用数状数组来求前面小于等于自己的数的个数k。(类似处理逆序对)然后用i-k得到大于自己的数。因为投掷距离的数据范围太大,但总数只有100000,所以需要对数据进行离散化才可使用数状数组。此题的和可能会大于int,所以需要用long long来处理。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll tt[101000];
int lowbit(int x){
    return x&(-x);
}
void add(int x){
    while(x<=100010){
        tt[x]++;
        x+=lowbit(x);
   }
}
ll sum(int x){
    ll k=0;
    while(x>0){
        k+=tt[x];
        x-=lowbit(x);
    }
    return k;
}
int a[100100];
int p[100100];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
        p[i]=a[i];
    }
    sort(a,a+n);
    int m=unique(a,a+n)-a-1;
    for(int i=0;i<n;i++){
        p[i]=lower_bound(a,a+m+1,p[i])-a+1;
    }
    ll ans1=0,ans2=0;
    for(int i=0;i<n;i++){
        ll f=sum(p[i]);
        ll w=i-f;
        ll ww=1+f-w;
        if(i%2==0)
        ans1+=ww;
        else ans2+=ww;
        add(p[i]);
    }
    if(ans1>ans2){
        printf("Calculus is hebei king!\\n");
    }
    else if(ans1<ans2){
        printf("huaji is hebei king!\\n");
    }
    else printf("hebei shuang king!\\n");
    return 0;
}

D.公园游玩

题意:在一个n*m的网格中,给出k个点,从(1,1)出发依次经过k个点后到达(n,m),在保证总路程和最短的前提下有多少种移动方案,你每次可以朝上下左右中的某一个方向移动一格。

思路:求从(1,1)到(X1, Y1)的移动方案数乘从(X1,Y1)到(X2,Y2)的方案数乘…乘( Xk,Yk)到(n,m)的方案数,即为答案。

由(1,1)到(n,m)的方案数的方程为 C ( n + m , n ) C(n+m,n) C(n+m,n)可以推出(X1,Y1)到(X2,Y2)的方案数为 C ( a b s ( X 1 − X 2 ) + a b s ( Y 1 − Y 2 ) , a b s ( X 1 − X 2 ) ) C(abs(X1-X2)+abs(Y1-Y2),abs(X1-X2)) C(abs(X1X2)+abs(Y1Y2),abs(X1X2))

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
ll fac[1000007];
ll ksm(ll x,ll p){
    ll res=1;
    while(p){
        if(p%2==1) res=res*x%mod;
        p/=2;
        x=x*x%mod;
    }
    return res;
}
ll inv(ll a) {
	return ksm(a,mod-2)%mod;
}
void solve() {
	fac[0] = 1;
	for(int i = 1;i<1000006; i++) {
		fac[i] = (fac[i-1]*i)%mod;
	}
}
ll comb(ll n,ll k){
	if(k>n)return 0;
	if(k==1)return n;
	return (fac[n]*inv(fac[k])%mod*inv(fac[n-k])%mod)%mod;
}
int main(){
    ll n,m,k;
    solve();
    scanf("%lld%lld%lld",&n,&m,&k);
    ll x=1,y=1;
    ll ans=1;
    ll a,b;
    for(int i=1;i<=k;i++){
        scanf("%lld%lld",&a,&b);
        ans=(ans*comb(abs(x-a)+abs(y-b),abs(x-a)))%mod;
        y=b,x=a;
    }
    ans=(ans*comb(abs(n-x+m-y),abs(m-y)))%mod;
    printf("%lld\\n",ans);
    return 0;
}

E.简单数论

先占个坑,还没补。

F.回文串

题意: 给出一个字符串,可以打乱顺序,问至少需要拆分成几个字符串才能使得拆分出的字符串都为回文串。

思路: 一个回文串中最多只能出现一个为奇数的字符,所以计算出奇数字符的个数即可。

#include<bits/stdc++.h>
using namespace std;
string str;
int p[30];
int main(){
    cin>>str;
    int k=str.size();
    for(int i=0;i<k;i++){
        p[str[i]-'a']++;
    }
    int ans=0;
    for(int i=0;i<26;i++){
        if(p[i]%2){
            ans++;
        }
    }
    printf("%d\\n",max(ans,1));//若ans为0,也需要至少一个串
    return 0;
}

G.数据结构好难啊

思路: 此题需要快速修改一个区域的值,我们使用差分算法进行处理,再计算出前缀积,但是需要快速查询一个区间的最大值,因为n的范围太大,无法使用线段树对区间最大值进行查询。但因为这题有一些性质,我们可以使用其他的方式进行计算。

性质1:若数组中未出现0,那么整个数组的前缀积的绝对值会逐渐变大。

性质2:若数组中出现0,那么后续所有的前缀积将全为0

所以我们可以使用数组预处理出距离 i i i点最近的前缀积大于0的下标。(该下标需小于等于 i i i点)

然后每次输入查询位置L和R的时候,我们先查询R得到一个下标,若该下表大于L,则说明该点在L,R之间,可以直接输出该点坐标,若小于则不合法,然后判断R点的前缀积是否为0,如果是则直接输出0,因为L,R区间内不存在大于0的前缀积了,如果R点的前缀积不为0,说明L,R区间数全为负数,又因为绝对值呈递增状态,所以L点的前缀积为最大值。(此题只能开到两个数组,否则会超内存)

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const ll mod=1e9+7;
ll d[30000100];
ll sum[30000100];
int main(){
    ll n,m1,m2;
    scanf("%lld%lld%lld",&n,&m1,&m2);
    int a,b,c;
    for(int i=1;i<=m1;i++){
        scanf("%d%d%d",&a,&b,&c);
        d[a]=(d[a]+c)%mod;
        d[b+1]=(d[b+1]-c)%mod;
    }
    int flag=0;
    sum[0]=1;
    for(int i=1;i<=n;i++){
        d[i]=(d[i]+d[i-1])%mod;
        sum[i]=sum[i-1]*d[i]%mod;
    }
    for(int i=1;i<=n;i++){
        if(sum[i]>0)flag=i;//记录坐标
        d[i]=flag;//记录i点之前最近的前缀积非零点位置
    }
    int x,y;
    for(int i=1;i<=m2;i++){
        scanf("%d%d",&x,&y);     
        if(d[y]>=x)printf("%lld\\n",sum[d[y]]);
        else if(sum[y]==0)printf("0\\n");
        else printf("%lld\\n",sum[x]);  
    }
    return 0;
}

J.有点复杂的gcd问题

思路: (看官方题解才知道)

f ( a 1 , a 2 , a 3 , . . . , a k ) = f(a1,a2,a3,...,ak)= f(a1,a2,a3,...,ak)= ∑ a 1 = 1 n \\sum_{a1=1}^n a1=1n ∑ a 2 = 1 a 1 \\sum_{a2=1}^{a1} a2=1a1 ∑ a 3 = 1 a 2 \\sum_{a3=1}^{a2} a3=1a2 ∑ a k = 1 a k − 1 \\sum_{ak=1}^{ak-1} ak=1ak1[ g c d ( a 1 , a 2 , a 3 , . . . , a k ) = = a 1 gcd(a1,a2,a3,...,ak)==a1 gcd(a1,a2,a3,...,ak)==a1]

因为 a 1 ≥ a 2 ≥ a 3 ≥ . . . ≥ a k a1≥a2≥a3≥...≥ak a1a2a3...ak,又 g c d ( a 1 , a 2 , a 3 , . . . , a k ) = = a 1 gcd(a1,a2,a3,...,ak)==a1 gcd(a1,a2,a3,...,ak)==a1 ,所以 a 1 = a 2 = a 3 = . . . = a k a1=a2=a3=...=ak a1=a2=a3=...=ak

即g( a 1 , a 2 , a 3 , . . . a k a1,a2,a3,...ak a1,a2,a3,...ak)可以化简为 ∑ a i = 1 n \\sum_{ai=1}^{n} ai=1n a 1 a1 a1

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
int main(){
    ll n,k,ans=0;
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=n;i++){
        ans=(ans+i)%mod;
    }
    printf("%lld\\n",ans);
    return 0;
}

k.星星拜年

题意: 给出n1个起点和n2个终点,求出从起点到终点的最少花费的路径上的最少点数为多少,输出k-最少点数。

思路: 既然已经给出最少花费为k,也就是起点到终点的最短路为k,那么建图只需要建立一个虚拟源点与所有的起点连接起来,再建一个虚拟终点与所有的终点连接起来,从源点跑一次最短路,在过程中更新经过的点数,这样得到的点数一定是最少的。

#include<bits/stdc++.h>
using namespace std;
#define pii pair<ll,ll>
#define mk make_pair
#define ll long long
ll n,m,x,y,k;
vector<pii>G[100010];
ll dist[100010],vis[100010],cnt[100010];
void dij(){
    for(int i=0;i<=n+1;i++){
        dist[i]=0x3f3f3f3f;
        cnt[i]=0x3f3f3f3f;
    }
    priority_queue<pii>q;
    dist[0]=0;
    cnt[0]=0;
    q.push(mk(0,0));
    while(!q.empty()){
        int t=q.top().second;
        q.pop();
        if(vis[t])continue;
        vis[t]=1;
        int k=G[t].size();
        for(int i=0;i<k;i++){
            int v = G[t][i].first;
            ll w = G[t][i].second;
            if(dist[v] > dist[t] + w) {
                dist[v] = dist[t] + w;
                cnt[v]=cnt[t]+1;//更新点数
                q.push(mk(-dist[v],v));
            }
            else if(dist[v]==dist[t] + w){
                cnt[v]=min(cnt[v],cnt[t]+1);
                q.push(mk(-dist[v],v));
            }
        }
    }
}
int main(){
    scanf("%lld%lld%lld%lld%lld",&n,&m,&x,&y,&k);
    ll a,b,c;
    for(int i=1;i<=x;i++){
        scanf("%lld",&a);
        G[0].push_back(mk(a,0));
    }
    for(int i=1;i<=y;i++){
        scanf("%lld",&a);
        G[a].push_back(mk(n+1,0));
    }
    for(int i=1;i<=m;i++){
        scanf("%lld%lld%lld",&a,&b,&c);
        G[a].push_back(mk(b,c));
    }
    dij();
    if(cnt[n+1]-1>=k){
        printf("we break up\\n");
    }
    else printf("%lld\\n",k-cnt[n+1]+1);
    return 0;
}

以上是关于(牛客2021)第一届河北工业大学程序设计竞赛(部分题解)的主要内容,如果未能解决你的问题,请参考以下文章

第 2 届河北省大学生程序设计竞赛(河北省赛)-Problem L. 跑图-题解

第 2 届河北省大学生程序设计竞赛(河北省赛)-Problem H. 神殿-题解

第 2 届河北省大学生程序设计竞赛(河北省赛)-Problem G. 520-题解

第 2 届河北省大学生程序设计竞赛(河北省赛)-Problem K. Bitmap-题解

第 2 届河北省大学生程序设计竞赛(河北省赛)-Problem C. icebound 的账单-题解

第 2 届河北省大学生程序设计竞赛(河北省赛)-Problem J. icebound 的商店-题解