为了能到远方,脚下的每一步都不能少.|

园龄:粉丝:关注:

牛客小白月赛110

A

模拟

点击查看代码
#include<bits/stdc++.h>

using namespace std;
int n;



int main(){
    cin>>n;
    int cnt=0;
    while(n>500){
        ++cnt;
        n-=500;
    }
    char c='A'+cnt;
    string s;
        while(n){
            s+=n%10+'0';
            n/=10;
        }
        while(s.size()<3){
            s=s+'0';
        }
        reverse(s.begin(),s.end());
    cout<<c<<s<<endl;
    return 0;
}

B

按照题意

点击查看代码
#include<bits/stdc++.h>

using namespace std;
string n,m;
string ans;

int main(){
    cin>>n>>m;
    for(int i=0;i<8;++i)
        if(n[i]==m[i]) ans+='g';
        else{
            if(n.find(m[i])!=-1) ans+='y';
            else ans+='r';
        }
    cout<<ans<<endl;
    if(ans.find('y')==-1) cout<<"congratulations"<<endl;
    else cout<<"defeat"<<endl;
    return 0;
}

C

这种题比较典

通常这种题,让你在满足条件的数组成的数列中,找出第k大的数,而且,这些数单调递增,很适合二分查找

以此题为例:
满足条件:

奇数同时,满足以下之一条件

1.以5为结尾的数
$ 5,15,25,......(a_n=5+(n-1)102.33 3,9,15,21,......(b_n=3+(n-1)6$

但敏锐地发现,15重复了,再我们确定第k大时,需要剔除重复以免误算

这里就可以用容斥定理
|A1A2|=|A1|+|A2||A1A2|

此时我们验证数m
(利用上述数列公式,推出小于m的个数)
以5结尾的奇数 |A1|=(m+5)/10
3的倍数的奇数 |A2|=(m+6)/6

15,45,75,......(cn=15+(n1)30)
满足上述条件的奇数|A1A2|=(m+15)/30

点击查看代码
#include<bits/stdc++.h>

using namespace std;
#define ll long long 
int t;

ll k;
bool check(ll x){
    
    //分别表示,满足“各个数位相加的和是3的倍数”
    //以5结尾
    //同时满足上述两个条件的个数
    ll a=(x+3)/6,b=(x+5)/10,c=(x+15)/30;
    if(a+b-c<k) return 1;
    return 0;
}
void solve(){
    ll l=3,r=4385714285;
    cin>>k;
    while(l<=r){
        ll mid=(l+r)>>1;
        if(check(mid)) l=mid+1;
        else r=mid-1;
    }
    cout<<l<<"\n";
    return ;
}
int main(){
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

D

提示很明显,长期主义者就是DP,短期主义就是贪心

而这样的选择方式,类似于区间DP

我们定义f[l][r]是长期主义者,选择剩下区间[l,r]时最大的收益
按照区间DP定义,从区间为1的长度开始转移,所以这个题的DP其实是这个题博弈的逆过程
初始状态f[i][i]=n&1?0:a[i]

  • 如果区间长度是奇数,最后选择的是贪心,所以f[i][i]就是0
  • 否则,f[i][i]=a[i]

状态转移看代码

点击查看代码
#include<bits/stdc++.h>

using namespace std;
#define ll long long 
int l,r;
const int maxn=1e3+10;
int f[maxn][maxn];
int n;
int a[maxn];
ll sum=0;

int main(){
    cin>>n;
    for(int i=1;i<=n;++i) cin>>a[i],sum+=a[i];
    for(int i=1;i<=n;++i) f[i][i]=n&1?0:a[i];//因为是奇数,所以最后选到i时是长期为0
    for(int i=2;i<=n;++i){
        
        for(int l=1;l+i-1<=n;++l){
            int r=l+i-1;
            if((n-i)&1) f[l][r]=max(a[l]+f[l+1][r],a[r]+f[l][r-1]);//此时区间长度是n-i,所以是长期主义者选择
            else f[l][r]=a[l]>=a[r]?f[l+1][r]:f[l][r-1];//贪心选择
        }
    }    
    cout<<sum-f[1][n]<<" "<<f[1][n]<<endl;//答案就很容易得到了
    return 0;
}

E

对于一个数组,只有下标ij=k或者|aiaj|=k,才能交换

所以我们直接想,如果一个数,能同时与两个其他的数交换,那么这三个都能交换,更抽象地想,能交换就说明有关系,就能抽象成图,能交换形成集合,于是可以用并查集记录可以交换的

进一步思考,既然交换后有序,那么排序后的数组和原数组的集合应该相同,也就是连通性相同,所以我们需要各自记录连通性,验证是否完全相同就行

而此时我们很容易发现无论是否排序,|aiaj|=k都是恒成立的,所以我们可以直接将满足该条件的形成一个并查集

只需要验证ij=k也是一定连通的,所以只需要用一个集合set记录下标相差为k的,将其连通

具体实现看代码

点击查看代码
#include<bits/stdc++.h>

using namespace std;
const int maxn=1e5+10;
int n,k;
int a[maxn],b[maxn];
int f[maxn];
map<int,int>mp;
multiset<int>x[maxn],y[maxn];

int find(int x){
    if(x!=f[x])  f[x]=find(f[x]);
    return f[x];
}
void solve(){
    cin>>n>>k;
    for(int i=1;i<=n;++i){
        f[i]=i;
        cin>>a[i];
        b[i]=a[i];
        mp[a[i]]=i;
    }
    
    for(int i=1;i<=n;++i){
        if(mp[a[i]+k])
            f[find(mp[a[i]+k])]=find(mp[a[i]]);//值相差为k可以交换,连通
    }
    for(int i=1;i<=n;++i)
        x[i%k].insert(find(i));//下标相差为k可以交换,加入连通块
    
    
    sort(b+1,b+1+n);
    for(int i=1;i<=n;++i)//有序数组的连通
        y[i%k].insert(find(mp[b[i]]));//下标相差为k的,加入b[i]所在在原数组中的位置的连通块
    //这里其实是一个反证法,假设,这两个集合是一样的,那么b[i]在原数组的下标所在的连通块,也一定与原数组所在的连通块相同,否则则不能转换
    
    for(int i=0;i<min(n,k);++i)
        if(x[i]!=y[i]){//有序序列的连通性和无序的相同才能转换
            puts("No");
            return ;
        }
    puts("Yes");
}
int main(){
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}

F

显然直接地找出每一对val(i,j)=t=ija[t]%p是超时的,而注意,问题问的是是否存在val(i,j)x(modp)

我们稍加转换val(i,j)=val(1,j)val(1,i1),也就是用前缀积转化

于是
val(1,j)val(1,i1)x(modp)

这里眼熟的小伙伴应该知道,可以用逆元:

val(1,j)×val(1,i1)1x(modp)

所以根据这个表达式,当我们固定右端点j时,若存在一个端点i,使得上式成立,那我们就应该修改a[i],最简单的方法就是修改为0,因为这样就不用担心后面的值累乘之后等于x,这样次数同时也最少

而当我们枚举到j时,前面val(1,i1)已经求出来了,所以这里就问题
就只需要验证val(1,i1)1是否存在就行

如果在思考,你会发现如果x=0,那么就不能修改为0,此时就要就数列中所有的数修改为不为0

点击查看代码
#include<bits/stdc++.h>

using namespace std;
#define ll long long

ll n,p,x;
const int maxn=1e5+10;
ll a[maxn];

ll quick_pow(ll a,ll b){
    ll base=a;
    ll ans=1;
    while(b){
        if(b&1) ans=ans*base%p;
        base=base*base%p;
        b>>=1;
    }
    return ans%p;
}
void solve(){
    cin>>n>>p>>x;
    for(int i=1;i<=n;++i)  cin>>a[i];
    
    if(x==0){
        for(int i=1;i<=n;++i) 
            if(a[i]==0) a[i]=1;
    }
    else {
        ll t=1;//前缀积
        ll inv=1;//逆元
        set<ll> s;
        for(int i=1;i<=n;++i){
            if(a[i]==0){
                t=inv=1;
                s.clear();
            }
            else {
                s.insert(inv);
                t=t*a[i]%p;
                inv=inv*quick_pow(a[i],p-2)%p;
                ll check=inv*x%p;
                if(s.count(check)){
                    a[i]=0;
                    inv=t=1;
                    s.clear();
                }
            }
        }
    }
    for(int i=1;i<=n;++i) cout<<a[i]<<" ";
    puts("");
    return ;
}

int main(){
    int t=1;
    while(t--) solve();
    return 0;
}

本文作者:归游

本文链接:https://www.cnblogs.com/guiyou/p/18717375

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   归游  阅读(17)  评论(2编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起