牛客周赛60 A~F全题解

牛客周赛60

给个链接

注释:

  • *角标表示比赛中我没有做出来的题目,

  • **表示我没有做出来,我也觉得我掌握很困难的题目。

希望给刚入门的一些帮助。

A. 困难数学题

题目描述略。

这题没啥意思,我们知道两个数互相异或是肯定0的,所以直接cout一个0就行了,比较无聊。

B. 构造序列

题目描述略。

正正不能相邻,负负不能相邻,就是说必须一正一负串成串儿。

那么长度应该是两两一组后多一个,或者正负相同数目的情况下直接就是2n。

#include<iostream>
using namespace std;
int main(){
    int n, m;
    cin>>n>>m;
    cout<<min(n, m)*2+(n!=m);
    return 0;
}

C. 连点成线

题目描述略。

让把横纵坐标相等的连成线。那么我们直接就把他们存成结构体,然后按x排个序算一遍最大值,然后再按y排个序算一遍最大值,写两个sort的cmp就解决了。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+100;
struct node{
    int x, y;
}a[N];
int n, m;

bool cmp1(node a, node b){
    return a.x<b.x || (a.x==b.x && a.y<b.y);
}
bool cmp2(node a, node b){
    return a.y<b.y || (a.y==b.y && a.x<b.x);
}
int net1(){
    int sig=-1, sy=0;
    int res=0;
    for(int i=1; i<=m; ++i){
        if(sig==-1 || sig!=a[i].x){
            sy=a[i].y;
            sig=a[i].x;
        }
        if(sig==a[i].x){
            res=max(res, a[i].y-sy);
        }
    }
    return res;
}
int net2(){
    int sig=-1, sx=0;
    int res=0;
    for(int i=1; i<=m; ++i){
        if(sig==-1 || sig!=a[i].y){
            sx=a[i].x;
            sig=a[i].y;
        }
        if(sig==a[i].y){
            res=max(res, a[i].x-sx);
        }
    }
    return res;
}
int main(){
//    int n, m;
    cin>>n>>m;
    for(int i=1; i<=m; ++i){
        cin>>a[i].x>>a[i].y;
    }
    sort(a+1, a+m+1, cmp1);
    int ans=net1();
    sort(a+1, a+m+1, cmp2);
    ans=max(net2(), ans);
    cout<<ans<<endl;
    return 0;
}

D. 我们N个真是太厉害了

题目描述略。

这道题非常的奇特,我第一眼以为是背包dp(动态规划)。

事实上它确实是dp,但是不是背包。

那么这个东西怎么分析呢。

我们手玩一下样例 1.1 1.1 1.1,我们发现,数据和位置无关,而且和找钱一样的道理,面值越小越不可替代。所以直接从小到大排个序。

然后验证一下是不是真的不可替代。手玩一下样例 2 , 3 , 4 , 5 2,3,4,5 2,3,4,5,我们发现 1 1 1确实是不能表示,所以肯定要有 a 1 = 1 a_1=1 a1=1

那么关注一下 a 2 a_2 a2,如果 a 2 = k a_2=k a2=k,我们能表示 k + 1 k+1 k+1 k k k 1 1 1,如果此时还没看出来再来一个 a 3 = k 2 a_3=k_2 a3=k2,那么就可以表示成 1 , k , k + 1 , k 2 , k + k 2 , k + k 2 + 1 1,k,k+1,k_2, k+k_2, k+k_2+1 1,k,k+1,k2,k+k2,k+k2+1

我们知道 k ≤ k 2 ⇒ k = 1   o r   2 k\leq k_2 \Rightarrow k=1 \, or\, 2 kk2k=1or2,那么也就是说,第 i i i位置的至少完成生成到 i i i的任务 = = 生成想法 ⇒ ==^{生成想法}\Rightarrow ==生成想法假设 第 i 个数能生成到 k 第i个数能生成到k i个数能生成到k = = 推导 = ⇒ ==^{推导}=\Rightarrow ==推导=⇒ i + 1 i+1 i+1个数能生成 a i ∼ k + a i a_i\sim k+a_i aik+ai。然后前面生成了 0 ∼ k 0\sim k 0k,如果 k + 1 < a i k+1 < a_i k+1<ai就直接寄了对吧。如果他俩无缝衔接就接着算并且宣布第 i + 1 i+1 i+1个数能生成到 k + a i k+a_i k+ai

如果有一瞬间能 n ≤ k n \leq k nk就结束了

很明显不需要收尾。

#include<iostream>
#include<algorithm>

using namespace std;
const int N=1e5+100;
int a[N];

void solve(){
    int n;
    cin>>n;
    for(int i=1; i<=n;++i){
        cin>>a[i];
    }
    sort(a+1, a+n+1);
    int sum=0;
    for(int i=1; i<=n; ++i){
        if(a[i]-sum >1){
            cout<<sum+1<<endl;
            return ;
        }
        sum=sum+a[i];
    
        if(sum>=n){
            cout<<"Cool!"<<endl;
            return ;
        }
    }
    
}
int main(){
    int t;
    cin>>t;c
    while(t--)solve();
    return 0;
}

E. 折返跑

题目略。

题目思路

这个题有个很明显的转化,就是转化为组合数学问题:在 [ 2 , n ] [2,n] [2,n]上画个线,三八线左面是左杆子,右面是右杆子,然后就直接 C l e f t m / 2 × C r i g h t m / 2 C_{left}^{m/2}\times C_{right}^{m/2} Cleftm/2×Crightm/2然后枚举线就行。但是这道题没给你限制那个总样例,这么写会被T掉。

所以要在这个上面再做转化。

很明显你就能想到,既然左面和右面的那个数量都是一定的,是不是只要把坑占好,自动就一半给左杆一半给右杆了。

因此这就是一个典型的球盒问题了。直接计算 C n − 2 m − 1 C_{n-2}^{m-1} Cn2m1就可以了。

题目思路—逆元

然后难点就是,这个b数怎么算的问题了对吧,而且还要模一个 M M M。乘我们很好解决,直接一边 × \times ×一边 m o d mod mod就完事了对吧。除怎么解决。

这里的解决方案是逆元 a ∗ a − 1 ≡ 1 ( m o d   M ) a*a^{-1}\equiv 1(mod\, M) aa11(modM)

理由是这样的:

∵ ( a % M × c % M ) % M = ( a × c ) % M , 又 ∵ a ∗ a − 1 ≡ 1 ( m o d   M ) \because (a\%M \times c \% M)\%M=(a\times c)\%M, 又\because a*a^{-1}\equiv 1(mod \, M) (a%M×c%M)%M=(a×c)%M,aa11(modM)

∴ ( c / a ) ∗ 1 % M = ( c / a ) ∗ a ∗ a − 1 % M = c ∗ a − 1 % M \therefore (c/a)*1\%M=(c/a)*a*a^{-1}\%M=c*a^{-1}\%M (c/a)1%M=(c/a)aa1%M=ca1%M

所以我们只需要知道这个数的逆元就能完成这个操作。

然后聊一聊怎么取逆元。

有一个东西叫做费马小定理,懒得翻没关系,它主要说了一个事情就是一个数 a a a关于 M M M的逆元就是 a M − 2 a^{M-2} aM2,不用知道怎么证,这个知识对正常人的发散性很弱,直接背就行了。

题目思路—快速幂

好了来吧, M = 1 0 9 + 7 M=10^9+7 M=109+7这玩意用循环算一算一个不吱声包超时的。

答案是快速幂,这个操作是怎么做的呢。

我们如果想计算 a 7 a^{7} a7,怎么计算呢。正常人是循环计算的。O人是这样计算的: a 7 = a 1 ∗ a 2 ∗ a 4 = a 1 ∗ ( a 1 ) 2 ∗ ( ( a 1 ) 2 ) 2 a^7=a^1*a^2*a^4=a^1*(a^1)^2*((a^1)^2)^2 a7=a1a2a4=a1(a1)2((a1)2)2,看到这个式子有没有一点感觉了对吧。也就是说,我们直接把 M M M变成二进制,那么我们只需要用一个 k k k记录每一个数位,然后每次迭代 k = k 2 k=k^2 k=k2,然后如果当前数位 M M M 1 1 1那么就将答案 ∗ k *k k,由于就算是 M = 1 0 18 M=10^{18} M=1018(这几乎是longlong的最大值)换成二进制也就只有63位,几乎是 O ( 1 ) O(1) O(1)的。

然后这里有两个板子需要读者背熟并敲熟。直接贴在下面了。

题目需求—模板

这是求 a a a关于 m o d mod mod的逆元的板子

long long inv(long long a, long long mod){
    long long res=1;
    long long k=a;
    long long g=mod-2;
    while(g!=0){
        if(g&1){
            res*=k;
            res%=mod;
        }
        k*=k;
        k%=mod;
        g>>=1;
    }
    res%=mod;
    return res;
}

把这个板子换个写法就是快速幂 ( a b ) % m o d (a^b)\%mod (ab)%mod的板子

long long inv(long long a, long long b, long long mod){
    long long res=1;
    long long k=a;
    while(b!=0){
        if(b&1){
            res*=k;
            res%=mod;
        }
        k*=k;
        k%=mod;
        b>>=1;
    }
    res%=mod;
    return res;
}

这两个板子请大家用熟,这很重要。

好了,这两个问题解决掉了,思路也说完了,所以直接贴码了。

#include<iostream>
using namespace std;
const long long mod=1000000007;
const int N=1e6+7;
int a[N];
long long cob[N];
long long tub[N];
long long inv(long long a, long long mod){
    long long res=1;
    long long k=a;
    long long g=mod-2;
    while(g!=0){
        if(g&1){
            res*=k;
            res%=mod;
        }
        k*=k;
        k%=mod;
        g>>=1;
    }
    res%=mod;
    return res;
}
long long phi[N];
long long c(int n, int m){
    if(m==0)return 1;
    if(m>n-m)m=n-m;
    long long res=1;
    for(int i=1, j=n; i<=m; ++i, --j){
        res*=j;
      //  cout<<j<<' '<<i<<endl;
        res%=mod;
        res*=phi[i];
        res%=mod;
    }
    return res;
}
void solve(){
    int n, m;
    cin>>n>>m;
    cout<<c(n-2, m-1)<<endl;
}
int main(){
    for(int i=1; i<N; ++i) phi[i]=inv(i, mod);  
	int t;
    cin>>t;
    while(t--)solve();
    
	return 0;
}

F. 口吃**

题目意思略掉。

这道题需要一点概率论知识。

因为这道题的 i d e a idea idea是官解,所以我也不太知道究竟用什么方法可以想出来。请大家自行体会。

我们认定 f [ i ] f[i] f[i]表示从第 i i i个字开始,说完所有话的期望。

那么先看第一个字,因为f1可以跳转到f1,也可以跳转到f2,并且跳转之后我们就多说了一个字,所以计算的方式是:

f 1 = a 1 a 1 + b 1 f 1 + a 1 a 1 + b 1 f 2 + 1 ⇒ f 1 = f 2 + a 1 + b 1 a 1 ( 1 ) f_1=\frac{a_1}{a_1+b_1}f_1+\frac{a_1}{a_1+b_1}f_2+1\Rightarrow f_1=f_2+\frac{a_1+b_1}{a_1} \qquad(1) f1=a1+b1a1f1+a1+b1a1f2+1f1=f2+a1a1+b1(1)

同理观察第 i i i个字:

f i = a i 2 ( a i + b i ) 2 f i + 1 + 2 a i b i ( a i + b i ) 2 f i + b i 2 ( a i + b i ) 2 f i − 1 + 1 ⇒ a i 2 + b i 2 ( a i + b i ) 2 f i = a i 2 ( a i + b i ) 2 f i + 1 + b i 2 ( a i + b i ) 2 f i − 1 + 1 ( 2 ) f_i=\frac{a_i^2}{(a_i+b_i)^2}f_{i+1}+\frac{2a_ib_i}{(a_i+b_i)^2}f_{i}+\frac{b_i^2}{(a_i+b_i)^2}f_{i-1}+1\Rightarrow \frac{a_i^2+b_i^2}{(a_i+b_i)^2}f_i=\frac{a_i^2}{(a_i+b_i)^2}f_{i+1}+\frac{b_i^2}{(a_i+b_i)^2}f_{i-1}+1\qquad(2) fi=(ai+bi)2ai2fi+1+(ai+bi)22aibifi+(ai+bi)2bi2fi1+1(ai+bi)2ai2+bi2fi=(ai+bi)2ai2fi+1+(ai+bi)2bi2fi1+1(2)

然后我们思索这样的一个问题。

我们从 f i f_i fi开始推,我们一定能把这个式子推导成只有 f i + 1 f_{i+1} fi+1 f 1 f_1 f1两个含 f f f剩下都是已知常数的一个式子。那么我们就可以这么搞了:直接把所有的已知常数和f_1一起混成两个参量P,Q,直接把这个式子写成 f i = P i f i + 1 + Q 1 f_i=P_if_{i+1}+Q_1 fi=Pifi+1+Q1的形式。那么很明显的由 ( 1 ) (1) (1) P 1 = 1 , Q 1 = a 1 + b 1 a 1 P_1=1, Q_1=\frac{a_1+b_1}{a_1} P1=1,Q1=a1a1+b1

那么我们继续推导 ( 2 ) (2) (2)

a i 2 + b i 2 ( a i + b i ) 2 f i = a i 2 ( a i + b i ) 2 f i + 1 + b i 2 ( a i + b i ) 2 f i − 1 + 1 ⇒ a i 2 + b i 2 ( a i + b i ) 2 f i = a i 2 ( a i + b i ) 2 f i + 1 + b i 2 ( a i + b i ) 2 ( P i − 1 f i + Q i − 1 ) + 1 ⇒ a i 2 + b i 2 − b i 2 P i − 1 ( a i + b i ) 2 f i = a i 2 ( a i + b i ) 2 f i + 1 + Q i − 1 ( a i + b i ) 2 + 1 ⇒ f i = a i 2 a i 2 + b i 2 − b i 2 P i − 1 f i + 1 + b i Q i − 1 + ( a i + b i ) 2 a i 2 + b i 2 − b i 2 P i − 1 \frac{a_i^2+b_i^2}{(a_i+b_i)^2}f_i=\frac{a_i^2}{(a_i+b_i)^2}f_{i+1}+\frac{b_i^2}{(a_i+b_i)^2}f_{i-1}+1\\\Rightarrow \frac{a_i^2+b_i^2}{(a_i+b_i)^2}f_i=\frac{a_i^2}{(a_i+b_i)^2}f_{i+1}+\frac{b_i^2}{(a_i+b_i)^2}(P_{i-1}f_i+Q_{i-1})+1\\\Rightarrow\frac{a_i^2+b_i^2-b_i^2P_{i-1}}{(a_i+b_i)^2}f_i=\frac{a_i^2}{(a_i+b_i)^2}f_{i+1}+\frac{Q_{i-1}}{(a_i+b_i)^2}+1\\\Rightarrow f_i=\frac{a_i^2}{a_i^2+b_i^2-b_i^2P_{i-1}}f_{i+1}+\frac{b_iQ_{i-1}+(a_i+b_i)^2}{a_i^2+b_i^2-b_i^2P_{i-1}} (ai+bi)2ai2+bi2fi=(ai+bi)2ai2fi+1+(ai+bi)2bi2fi1+1(ai+bi)2ai2+bi2fi=(ai+bi)2ai2fi+1+(ai+bi)2bi2(Pi1fi+Qi1)+1(ai+bi)2ai2+bi2bi2Pi1fi=(ai+bi)2ai2fi+1+(ai+bi)2Qi1+1fi=ai2+bi2bi2Pi1ai2fi+1+ai2+bi2bi2Pi1biQi1+(ai+bi)2

然后到这里为止。我们就知道了新的 P i 和 Q i P_{i}和Q_i PiQi的求法了,即 P i = a i 2 a i 2 + b i 2 − b i 2 P i − 1 , Q i = b i Q i − 1 + ( a i + b i ) 2 a i 2 + b i 2 − b i 2 P i − 1 P_i=\frac{a_i^2}{a_i^2+b_i^2-b_i^2P_{i-1}}, Q_i=\frac{b_iQ_{i-1}+(a_i+b_i)^2}{a_i^2+b_i^2-b_i^2P_{i-1}} Pi=ai2+bi2bi2Pi1ai2,Qi=ai2+bi2bi2Pi1biQi1+(ai+bi)2

最后,我们知道 f n = 1 f_n=1 fn=1

因此通过每次迭代 f i = P i ∗ f i + 1 + Q i f_i=P_i*f_{i+1}+Q_i fi=Pifi+1+Qi求得 f 1 f_1 f1为题目答案。

#include<iostream>
using namespace std;
const int mod=1e9+7;
typedef long long ll;
ll inv(ll x){
    ll m=mod-2;
    ll k=x;
    ll res=1;
    while(m!=0){
        if(m&1)res*=k, res%=mod;
        k*=k;
        k%=mod;
        m/=2;
    }
    return res;
}
struct node{
    ll x;
    friend node operator*(node a, node b){
        return {a.x*b.x%mod};
    }    
    friend node operator/(node a, node b){
        return {a.x*inv(b.x)%mod};
    }
    friend node operator+(node a, node b){
        return {(a.x+b.x)%mod};
    }
    friend node operator-(node a, node b){
        return {(a.x-b.x+mod)%mod};
    }
};
const int N=1e5+100;
node a[N], b[N];
node p[N], q[N];
node f[N];

int main(){
    int n;
    cin>>n;
    for(int i=1; i<n; ++i)cin>>a[i].x;
    for(int i=1; i<n; ++i)cin>>b[i].x;
    
    p[1]={1};
    q[1]=(a[1]+b[1])/a[1];
    for(int i=2; i<n; ++i){
        node A=a[i]*a[i];
        node B=b[i]*b[i];
        node S=(a[i]+b[i])*(a[i]+b[i]);
        node M=A+B-p[i-1]*B;
        p[i]=A/M;
        q[i]=(B*q[i-1]+S)/M;
    }
    f[n]={1};
    for(int i=n-1; i>=1; --i){
        f[i]=f[i+1]*p[i]+q[i];
    }
    cout<<f[1].x;
    return 0;
}

赛后总结

这次题目感觉比小白月赛101要难,但是前五题非常经典,实例比较代表性。F确实很复杂,期望dp确实没有很常遇到过。感谢牛客respect。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值