2020牛客寒假算法基础集训营2

链接:https://ac.nowcoder.com/acm/contest/3003#question link
来源:牛客竞赛

A 做游戏

题目描述

牛牛和 牛可乐进行了多轮游戏, 牛牛总共出了 A 次石头,B 次剪刀,C 次布;牛可乐总共出了 X 次石头,Y 次剪刀,Z 次布。 你需要求出 牛牛最多获胜多少局。

输入描述

第一行,三个非负整数 A , B , C A,B,C A,B,C
第二行,三个非负整数 X , Y , Z X,Y,Z X,Y,Z
保证 A + B + C = X + Y + Z A+B+C=X+Y+Z A+B+C=X+Y+Z, 0 0 0 A , B , C , X , Y , Z A,B,C,X,Y,Z A,B,C,X,Y,Z 1 0 9 10^9 109

思路

贪心即可

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
ll a,b,c,x,y,z; 
int main(){
scanf("%lld%lld%lld%lld%lld%lld",&a,&b,&c,&x,&y,&z);
printf("%lld\n",min(a,y)+min(b,z)+min(c,x));     
}//Accepted!

B 排数字

题目描述

牛可乐得到了一个纯数字的字符串 S S S,他想知道在可以任意打乱 S S S 顺序的情况下,最多有多少个不同的子串为 616 616 616

输入描述

第一行,一个正整数 ∣ S ∣ |S| S,表示 S S S的长度
第二行,一个字符串 S S S,其字符集为{0,1,2,3,4,5,6,7,8,9}。
保证 1 ≤ ∣ S ∣ ≤ 2 × 1 0 5 1≤∣S∣≤2×10^5 1S2×105

思路

贪心,即将所有的 1 , 6 1,6 1,6排成 6161616... 6161616... 6161616...即可

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
int len;char s[maxm];
int one , six;
int main(){
scanf("%d",&len);
scanf("%s",s+1);
rep(i,1,len){    
if(s[i] == '1') one++;    
if(s[i] == '6') six++;
} 
six--;
printf("%d",min(one,six) >= 0 ? min(one,six) : 0);     
}//Accepted!

C 算概率

题目描述

牛牛刚刚考完了期末,尽管 牛牛 做答了所有 n n n 道题目,但他不知道有多少题是正确的。
不过,牛牛 知道第 i i i 道题的正确率是 p i p_i pi​。
牛牛 想知道这 n n n 题里恰好有 0 , 1 , … , n 0,1,…,n 0,1,,n 题正确的概率分别是多少,对 1 0 9 + 7 10^9+7 109+7 取模。

输入描述

第一行,一个正整数 n n n
第二行, n n n 个整数 p 1 , p 2 , … , p n p_1,p_2,…,p_n p1,p2,,pn,在模 1 0 9 + 7 10^9+7 109+7 意义下给出。
保证 1 ≤ n ≤ 2000 1≤n≤2000 1n2000

思路

我们记从 p 1 , p 2 , . . . , p n p_1,p_2,...,p_n p1,p2,...,pn中取出 k k k个相乘,这样所有的组合之和为 P k P_k Pk。我们会发现这 n n n个题中恰好 k k k个题对的概率是 P k − C k + 1 k ∗ P k + 1 + . . . + ( − 1 ) n − k ∗ C n k ∗ P n P_k -C_{k+1}^k*P_{k+1}+...+(-1)^{n-k}*C_n^k*P_n PkCk+1kPk+1+...+(1)nkCnkPn。于是问题转换为求解 P k P_k Pk C n k C_n^k Cnk。我们很自然的有递推式 C n k = C n − 1 k − 1 + C n − 1 k C_n^k=C_{n-1}^{k-1}+C_{n-1}^k Cnk=Cn1k1+Cn1k以及 P ( n , k ) = P ( n − 1 , k ) + p i ∗ P ( n − 1 , k − 1 ) P(n,k) = P(n-1,k)+p_i*P(n-1,k-1) P(n,k)=P(n1,k)+piP(n1,k1)。(其中 P ( m , k ) P(m,k) P(m,k)表示的意思是从 p 1 , p 2 , . . . , p m p_1,p_2,...,p_m p1,p2,...,pm中取出 k k k个相乘,这样所有的组合之和)

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define maxn 100005
#define maxm 400005
#define mod 1000000007
ll p[maxn] ;
int n;
ll P[2005][2005];
ll c[2005][2005];
void get(){    
c[0][0] = 1;    
c[1][0] = 1, c[1][1] = 1;    
rep(i,2,2000){        
c[i][0] = 1;        
c[i][i] = 1;        
rep(j,1,i-1) c[i][j] = (c[i-1][j-1] + c[i-1][j])%mod;    
}    
P[0][0] = 1;    
rep(i,1,2000){        
P[i][0] = 1;        
P[i][i] = (P[i-1][i-1]*p[i])%mod;        
rep(j,1,i-1) P[i][j] = (P[i-1][j] +p[i]*P[i-1][j-1]%mod)%mod;     
}} 
int main(){
scanf("%d",&n);
rep(i,1,n) scanf("%lld",&p[i]);
get();
rep(i,0,n){    
ll ans = 0;    
int sign = 1;    
rep(j,i,n){        
ans = (ans + c[j][j-i]*P[n][j]*sign)%mod;        
sign = sign*(-1);             
}    
if(ans < 0) ans += mod;    
printf("%lld ",ans);} 
}//Accepted!

D 数三角

题目描述

n n n个不重合的点,问这些点构成的三角形中有多少个钝角三角形

输入描述

第一行,一个正整数 n n n,表示点数。

第二行至第 n + 1 n+1 n+1 行中,第 i + 1 i+1 i+1 行包含两个整数 x i x_i xi, y i y_i yi,表示第 i i i 个点的坐标。

保证 1 ≤ n ≤ 500 1\leq n\leq 500 1n500 − 1 0 4 ≤ x i , y i ≤ 1 0 4 -10^4\leq x_i,y_i\leq 10^4 104xi,yi104,任意两点不重合。

思路

水题,枚举所有三角形,点乘判断钝角即可

AC代码

#include<stdio.h>
#include<algorithm>
#define maxn 501
using namespace std;
int n;
struct Point{
    int x,y;
    inline Point(int x=0,int y=0):x(x),y(y){}
}p[maxn];
Point operator + (Point A,Point B){
    return Point(A.x+B.x,A.y+B.y);
}
Point operator - (Point A,Point B){
    return Point(A.x-B.x,A.y-B.y);
}
int operator * (Point A,Point B){
    return A.x*B.x+A.y*B.y;
}
int Cross(Point A,Point B)  // 计算叉积
{
    return A.x*B.y-A.y*B.x;
}
int ans;
int check(int i,int j,int k){
    if((p[i]-p[j])*(p[k]-p[j])<0&&Cross(p[i]-p[j],p[k]-p[j])!=0)return 1;
    if((p[i]-p[k])*(p[j]-p[k])<0&&Cross(p[i]-p[k],p[j]-p[k])!=0)return 1;
    if((p[j]-p[i])*(p[k]-p[i])<0&&Cross(p[j]-p[i],p[k]-p[i])!=0)return 1;
    return 0;
}
int main(){
    scanf("%d",&n);
    int a,b;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a,&b);
        p[i]=Point(a,b);
    }
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            for(int k=j+1;k<=n;k++){
                ans+=check(i,j,k);
            }
        }
    }
    printf("%d",ans);
    return 0;
}

E 做计数

题目描述

给定n,求有多少个不同的正整数三元组 ( i , j , k ) \text{}(i,j,k) (i,j,k)满足 i + j = k \sqrt i+\sqrt j=\sqrt k i +j =k ,且 i × j ≤ n i\times j\leq n i×jn
当两个三元组 ( i 1 , j 1 , k 1 ) \text{}(i_1,j_1,k_1) (i1,j1,k1), 满足 i 1 ≠ i 2 i_1\neq i_2 i1=i2 j 1 ≠ j 2 j_1\neq j_2 j1=j2 k 1 ≠ k 2 k_1\neq k_2 k1=k2 时它们被认为是不同的。

输入描述

第一行,一个正整数 n n n
保证 1 ≤ n ≤ 4 × 1 0 7 1\leq n\leq 4\times 10^7 1n4×107

思路

确定 i i i j j j即可确定 k k k,且 k k k为整数,容易发现当左边的式子为 a x + b x a\sqrt x +b\sqrt x ax +bx 时有这样的整数 k k k
于是我们枚举 x x x,计算二元组 ( a , b ) (a,b) (a,b)的个数
注意到诸如 8 \sqrt 8 8 在枚举 x = 2 x=2 x=2时已经计算过,所以为了避免重复,我们枚举的 x x x不能是完全平方数的倍数,可以预处理掉

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define mst(s) memset(s,0,sizeof(s))
#define ls(x) x<<1
#define rs(x) x<<1|1
#define lb(x) x&-x
#define pii pair<int,int>
#define pli pair<ll,int>
#define pll pair<ll,ll>
#define pi acos(-1)
#define F first
#define S second
#define cuts printf("\n--------------\n");
#define maxn 40000001
#define maxm 400005
 
int n, m,p, ans = 0;
int vis[maxn];
int main(){
scanf("%d",&n);
m=(int)sqrt(n);
for(int i=2;i<=m;i++){
    int j=1,k=i*i;
    while(k*j<=n){
        vis[k*j]=1;
        j++;
    }
}
rep(i,1,m){
    if(vis[i]==1)continue;
    rep(j,1,m/i)ans+=(m/i)/j;
}
printf("%d",ans);
}

F 拿物品

链接:https://ac.nowcoder.com/acm/contest/3003/F
来源:牛客网

题目描述

牛牛和 牛可乐 面前有 n 个物品,这些物品编号为 1 , 2 , … … , n 1,2,……,n 1,2,n,每个物品有两个属性 a a a i i i, b b b i i i
牛牛与 牛可乐会轮流从剩下物品中任意拿走一个, 牛牛先选取。

设 牛牛选取的物品编号集合为 H H H,牛可乐选取的物品编号的集合为 T T T,取完之后,牛牛 得分为 Σ \Sigma Σ i ∈ H i\in\mathbb H iH a a a i i i;而 牛可乐得分为 Σ \Sigma Σ i ∈ T i\in\mathbb T iT b b b i i i
牛牛和 牛可乐都希望自己的得分尽量比对方大(即最大化自己与对方得分的差)。

你需要求出两人都使用最优策略的情况下,最终分别会选择哪些物品,若有多种答案或输出顺序,输出任意一种。

思路

a a a i i i b b b i i i之和从大到小排序,轮流从大到小取。

代码

//
//  main.cpp
//  cpp
//
//  Created by ZhuChenyu on 2019/11/08.
//  Copyright  2019年 ZhuChenyu. All rights reserved.
//
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define MOD 1000007
#define maxn 200020
#define INF 2147483647
typedef unsigned float_bits;
 
using namespace std;
 
//priority_queue <int,vector<int>,greater<int> > que;
 
struct wayy
{
    int num,m;
}c[maxn];
 
int n;
int a[maxn]={0},b[maxn]={0};
 
int cmp(struct wayy x,struct wayy y)
{
    if (x.m>y.m) return 1; else return 0;
}
 
int main()
{
    int i,j;
    scanf("%d",&n);
    for (i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for (i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
    }
    for (i=1;i<=n;i++)
    {
        c[i].m=a[i]+b[i];
        c[i].num=i;
    }
 
    sort(c+1,c+n+1,cmp);
 
    /*for (i=1;i<=n;i++)
    {
        printf("%d %d\n",c[i].num,c[i].m);
    }*/
 
    for (i=1;i<=n;i++)
    {
        if (i%2==1) printf("%d ",c[i].num);
    }
    printf("\n");
    for (i=1;i<=n;i++)
    {
        if (i%2==0) printf("%d ",c[i].num);
    }
    printf("\n");
 
    return 0;
}
/*3
7 6 8
4 2 5*/

G 判正误

牛可乐有七个整数 $a,b,c,d,e,f,g $并且他猜想 a d + b e + c f = g a^d+b^e+c^f=g ad+be+cf=g。但牛可乐无法进行如此庞大的计算。
请验证 牛可乐的猜想是否成立。

输入描述

第一行一个正整数 T T T,表示有 T T T 组数据。
每组数据输入一行七个整数 a , b , c , d , e , f , g a,b,c,d,e,f,g a,b,c,d,e,f,g
保证 1 ≤ T ≤ 1000 1≤T≤1000 1T1000, − 1 0 9 ≤ a , b , c , g ≤ 1 0 9 −10^9 ≤a,b,c,g≤10^9 109a,b,c,g109 0 ≤ d , e , f ≤ 1 0 9 0≤d,e,f≤10^9 0d,e,f109 保证不会出现指数和底数同为 0 的情况。

思路

哈希即可

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
ll a, b, c ,d ,e ,f, g;
const int mod[100]={2,3,5,7,11,13,23,29,31,37,41,43,59,61,67,71,73,79,97,101,103,107,109,113,137,139,149,151,157,163,163,179,181,191,193,197,199,227,229,233,239,241,251,269,271,277,281,283,293,313,317,331,337,347,349,367,373,379,383,389,397};
ll ksm(ll s,ll x,ll p){    
ll res = 1;    
while(x){        
if(x&1) res = res*s%p;        
x >>= 1;        
s = s*s%p;    
}    
return res;
}  
int main(){    
int t;    
scanf("%d",&t);   
 while(t--){        
 scanf("%lld%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&e,&f,&g);        
 int ok = 1;        
 rep(i,0,59){            
 if((((ksm(a,d,mod[i]) + ksm(b,e,mod[i]) + ksm(c,f,mod[i])) % mod[i]) + mod[i])%mod[i] != ((g%mod[i]) + mod[i])%mod[i]){                
 ok = 0;                
 break;            
 }                     
 }         
 if(ok) printf("Yes\n");        
 else printf("No\n");       
 }       
 }//Accepted!

H 施魔法

题目描述

给定 n n n个元素,第 i i i个元素的值为 a i a_i ai,从中多次选择至少 k k k个元素,费用是这些元素中的最大值减去最小值,保证每个元素都被选择且仅被选择一次,求最小费用

输入描述

第一行两个正整数 n , k n, k n,k
第二行 n n n 个整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an
保证 1 ≤ k ≤ n ≤ 3 × 1 0 5 1\leq k\leq n\leq 3\times 10^5 1kn3×105 0 ≤ a i ≤ 1 0 9 0\leq a_i\leq 10^9 0ai109

思路

显然应该先排序,每次选择的元素都是连续的可以保证最小,并且选择次数尽可能地多才可能达到费用最小。
反向考虑,对排好序的数列切 m m m刀,刀与刀之间的距离至少为 k k k,在 a j , a j + 1 a_j,a_{j+1} aj,aj+1之间切一刀可以使总费用减少 a j + 1 − a j a_{j+1}-a_j aj+1aj,显然 d p dp dp可做
转移方程式为
f [ i ] = m a x j ∈ [ 1 , i − k ] { f [ j ] } + b [ i ] f[i]=max_{j\in [1,i-k]}\{f[j]\}+b[i] f[i]=maxj[1,ik]{f[j]}+b[i]
f [ i ] f[i] f[i]表示最后一刀切在 i i i处的最大减少费用, b [ i ] b[i] b[i]表示差值
其中最大值的计算可以在转移过程中不断更新,时间复杂度 O ( n ) O(n) O(n)

AC代码

#include<stdio.h>
#include<algorithm>
#define maxn 300001
using namespace std;
int a[maxn],b[maxn];
int f[maxn];
int ans,n,k,now;
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    sort(a+1,a+n+1);
    ans=a[n]-a[1];
    for(int i=1;i<=n-1;i++){
        b[i]=a[i+1]-a[i];
    }
    int front=0;
    for(int i=k;i<=n-k;i++){
        front=max(front,f[i-k]);
        f[i]=front+b[i];
        now=max(now,f[i]);
    }
    printf("%d",ans-now);
}

I 建通道

题目描述

给定 n n n个带权节点,权值为 v v v,点 i i i与点 j j j之间建边的费用为 l o w b i t ( v i ⊕ v j ) lowbit(v_i \oplus v_j) lowbit(vivj),求使这 n n n个点互相连通的最小花费

输入描述

第一行,一个正整数 n n n
第二行, n n n 个非负整数 v 1 , v 2 , … , v n v_1,v_2,\dots,v_n v1,v2,,vn
保证 1 ≤ n ≤ 2 × 1 0 5 1\leq n\leq 2\times 10^5 1n2×105 0 ≤ v i < 2 30 0\leq v_i < 2^{30} 0vi<230

思路

乍一看类似gcd最小生成树,其实是智商题,我们对所有的二进制 v v v从最后一位往前扫,找到第一个既有0又有1的位,将0和1相连,连接 n − 1 n-1 n1条边,答案就是此时的边权 × ( n − 1 ) \times (n-1) ×(n1)
但对于相同的数,显然相同的数相连的边权的0是最小的,所以加个去重操作就好了

AC代码

#include<stdio.h>
#include<algorithm>
#include<math.h>
#define ll long long
#define  maxn 200005
using namespace std;
int n;
ll v;
int f[40][5];
int zn;
ll ans;
ll x[maxn];
int main(){
    scanf("%d",&n);
    ans=n-1;
    for(int i=1;i<=n;i++){
        scanf("%lld",&v);
        x[i]=v;
        int p=1;
        while(v>0){
            f[p][v&1]=1;
            p++;
            v>>=1;
        }
        for(int i=p;i<=30;i++){
            f[i][0]=1;
        }
    }
    sort(x+1,x+n+1);
    for(int i=2;i<=n;i++){
        if(x[i]==x[i-1])ans--;
    }
    for(int i=1;i<=30;i++){
        if(f[i][0]==1&&f[i][1]==1){
            printf("%lld",ans);
            return 0;
        }
        ans=ans*2;
    }
    printf("0");
    return 0;
}

J 求函数

题目描述

牛可乐有 n n n 个一次函数,第 i i i 个函数为 f i ( x ) = k i ∗ x + b i f_i(x)=k_i * x+b_i fi(x)=kix+bi 。 牛可乐有 m m m 次操作,每次操作为以下二者其一:
• 1 i k b 将 f i ( x ) f_i(x) fi(x) 修改为 f i ( x ) = k × x + b f_i(x)=k×x+b fi(x)=k×x+b
• 2 l r 求 f r ( f r − 1 ( ⋯ ( f l + 1 ( f l ( 1 ) ) ) ⋯   ) ) f_r(f_{r−1}(⋯(f_{l+1}(f_l(1)))⋯ )) fr(fr1((fl+1(fl(1)))))
牛可乐当然(bu)会做啦,他想考考你——
答案对 1 0 9 + 7 10^9+7 109+7 取模。

输入描述

第一行,两个正整数 n , m n,m n,m
第二行, n n n 个整数 k 1 , k 2 , … , k n k_1,k_2,…,k_n k1,k2,,kn
第三行, n n n 个整数 b 1 , b 2 , … , b n b_1,b_2,…,b_n b1,b2,,bn​。
接下来 m m m 行,每行一个操作,格式见上。
保证 1 ≤ n , m ≤ 2 × 1 0 5 1≤n,m≤2×10^5 1n,m2×105

思路

可以观察到要求的式子为 k l ∗ k l + 1 ∗ . . . ∗ k r + k r ∗ k r − 1 ∗ . . . k l + 1 ∗ b l + . . . + b r k_l*k_{l+1}*...*k_r+k_r*k_{r-1}*...k_{l+1}*b_l+...+b_r klkl+1...kr+krkr1...kl+1bl+...+br。我们维护分别维护 k l ∗ k l + 1 ∗ . . . ∗ k r k_l*k_{l+1}*...*k_r klkl+1...kr k r ∗ k r − 1 ∗ . . . k l + 1 ∗ b l + . . . + b r k_r*k_{r-1}*...k_{l+1}*b_l+...+b_r krkr1...kl+1bl+...+br。直接使用线段树单点修改,区间查询即可。

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define ls(x) x<<1
#define rs(x) ((x<<1)|1) 
#define maxn 210000 
const int mod = 1e9 +  7;
int n , m , l[maxn<<2],r[maxn<<2],mid[maxn<<2],l1,r1;
ll k[maxn], b[maxn], f[maxn<<2], g[maxn<<2], k0, b0, ansf, ansg; 
void up(int x){    
f[x] = (f[ls(x)]*f[rs(x)])%mod;    
g[x] = (g[ls(x)]*f[rs(x)] + g[rs(x)])%mod;
} 
void build(int x,int l0,int r0){    
l[x] = l0;    
r[x] = r0;    
if(l0 == r0){        
f[x] = k[l0];        
g[x] = b[l0];        
return;    
}    
mid[x] = (l[x] + r[x]) >> 1;    
build(ls(x),l[x],mid[x]);    
build(rs(x),mid[x]+1,r[x]);    
up(x);} void update(int x){    
if(l[x] == r[x]){        
f[x] = k0;        
g[x] = b0;        
return;    
}    
if(l1 <= mid[x]) update(ls(x));    
else update(rs(x));    
up(x);
} 
void query(int x){    
if(l1 <= l[x] && r[x] <= r1){        
ansf = ansf*f[x] % mod;        
ansg = (ansg * f[x] + g[x])%mod;        
return;    
}
if(l1 <= mid[x]) query(ls(x));    
if(r1 > mid[x]) query(rs(x));}   
int main(){    
scanf("%d%d",&n,&m);    
rep(i,1,n) scanf("%lld",&k[i]);    
rep(i,1,n) scanf("%lld",&b[i]);    
build(1,1,n);    
while(m--){        
int op;        
scanf("%d",&op);        
if(op == 1){            
scanf("%d%lld%lld",&l1,&k0,&b0);            
update(1);        
}        
else{            
scanf("%d%d",&l1,&r1);           
ansf = 1;            
ansg = 0;            
query(1);            
printf("%lld\n",(ansf+ansg)%mod);        
}    
}return 0;    
}//Accepted!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值