Codeforces Round #538 (Div. 2)A~F个人题解

本文介绍了四种编程挑战:解决葡萄分配问题以满足不同人的需求、最优数组划分以保持长度平衡、查找数制中尾部零的个数以及高效处理数组操作和查询。通过贪心、排序和数论技巧,展示了如何编写相应的代码并优化解决方案。
摘要由CSDN通过智能技术生成

A. Got Any Grapes?

题意:有3种葡萄,第一种有a个,第二种有b个,第三种有c个,现在有三个人,第一个人要吃x个,但是只能吃第一种,第二个人要吃y个,但是只能吃第一种和第二种,第三个人要吃z个,他什么都能吃,问能满足所有人的需要吗?

知识点:贪心

思路:我们先让第一个人吃,再让第二个人吃,最后全给第三人,如果过程中都满足了,才能满足所有人的需要。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e6+5;
void solve(){
    vector<int>a(3),b(3);
    for(int i=0;i<3;++i)cin>>a[i];//每个人的需要
    for(int i=0;i<3;++i)cin>>b[i];//每个葡萄的个数
    if(b[0]<a[0])cout<<"NO\n";//如果第一个人都不满足,那一定不满足
    else {
        b[0]-=a[0];//第一个人吃完
        if(b[0]+b[1]<a[1])cout<<"NO\n";//第二个人不满足
        else {
            if(b[0]>=a[1]){//如果第二个人只吃第一种葡萄就满足了
                b[0]-=a[1];
                if(b[0]+b[1]+b[2]<a[2])cout<<"NO\n";//第三人不满足
                else cout<<"YES\n";
            }
            else {//第二个人要吃第一种和第二种
                a[1]-=b[0];
                b[1]-=a[1];
                if(b[1]+b[2]<a[2])cout<<"NO\n";//第三人不满足
                else cout<<"YES\n";
            }
        }
    }
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

B. Yet Another Array Partitioning Task

题意:有一个长度为n的数组a,现在要把这个数组划分为k段,然后问你怎么划分,能使划分后,每段的长度大于等于m,并且每段的最大m个数的和加起来最大?

知识点:思维,排序

思路:最后最大的和一定是数组a中最大的m*k个数的和,所以我们先排个序,确定最大的m*k个数在哪里,然后从i=1的位置依次往右移动,每次遇到一个最大的m*k的数中的一个就++,如果个数为m了,就划分当前位置,然后从后面重新开始,最后判一下最后一段就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2e5+5;
bool vis[N];
pair<int,int>a[N];//排序要下标
int b[N];
void solve(){
    int n,m,k;cin>>n>>m>>k;
    for(int i=1;i<=n;++i){
        cin>>b[i];
        a[i].first=b[i];
        a[i].second=i;
    }
    sort(a+1,a+n+1);
    ll w=0;
    for(int i=n-m*k+1;i<=n;++i){
        vis[a[i].second]=true;//最大的m*k个数的下标标记
        w+=a[i].first;//答案求和
    }
    cout<<w<<'\n';
    int res=0,cnt=0;
    for(int i=1;i<=n;++i){
        if(vis[i]){
            res++;//最大m*k的数的个数++
            if(res==m){
                cnt++;//划分组数++
                if(cnt==k)continue;//如果已经有k组了,就可以退了
                cout<<i<<' ';
                res=0;//清空
            }
        }
    }
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

C. Trailing Loves (or L'oeufs?)

题意:给你一个数n,问你n!在b进制下末尾有多少个0?

知识点:数论,数学

思路:b进制下有末尾有多少个0,就是问n!整除b^w情况下,最大的w是多少,因为在b进制下,我们可以把一个数看成 \sum_{i=0}a_i \times b^i,a_i是每一位前的系数,末尾是0,说明a_i是0,直到一个a_i不是0,说明找完了,也找到了一个对应的i,就是末尾0的个数。

对b质因数分解,假设分解后b=\prod _{i=1}p_i^{w_i},p_i是第i个质数,w_i是第i个质数的个数,

然后在n!中找这些质数的个数,具体是这样cnt_j=\sum _{i=1}\left \lfloor \frac{n}{p_{j}^i} \right \rfloor,cnt_j是1到n中关于b的第j个质数的个数。(比如找2的个数,先看所以2的倍数,再看4的倍数,再看8的倍数.......)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2e5+5;
void solve(){
    ll n,b;cin>>n>>b;
    vector<pair<ll,int> >p;
    for(ll i=2;i*i<=b;++i){//对b质因子分解
        if(b%i==0){
            int cnt=0;
            while(b%i==0)b/=i,cnt++;
            p.push_back({i,cnt});//记录质因子和个数
        }
    }
    if(b>1)p.push_back({b,1});
    ll ans=1e18;
    for(auto [pi,w]:p){
        ll res=0;
        for(ll j=pi;j<=n;j*=pi){//pi pi^2 pi^3....都看
            res+=(n/j);
            if(j>=n/pi+1)break;//这里一定要加,不然会有j*pi炸long long的情况
        }
        ans=min(ans,res/w);//找答案
    }
    cout<<ans<<'\n';
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

D. Flood Fill

题意:有一个长度为n的数组,定义对于任意一对(i,j),如果对于任意i<k<j,满足a[i]=a[j],a[i]=a[k],a[j]=a[k],说明这一段是一个联通块。现在要我们任选一个点开始,每次可以让包含我们开始选的点的情况下,把这个联通块变成任意值,问最小的操作数,让整个数组属于一个联通块。

知识点:DP

思路:很容易想到枚举每一个点作为开始,看当前点开始后的最小操作数,让整个数组属于一个联通块,然后发现,我们一定能在n-1次操作下满足条件,每次啥都不管就硬变。如果要让操作数减少一定会是我们当前左右俩是相同的值,比如这样

黄色框住的是开始位置,现在我们可以变成1,这样俩边的1就是包含主了,操作数就少了1

怎么能让这样的情况最多呢?

比如这种,我们在红色位置,我们把后面的字符反转一下,拼接到前面的下面

 这时候,我们选 7  4   3 和 7  5  3 都能让这样的情况最多,这就变成了有两个序列(我们设上面是a序列,下面是b序列),我们要找这两个序列的最长相等子序列,这是一个经典的DP问题,

DP方程是

if \ a_i==b_j,dp[i][j]=dp[i-1][j-1]+1

else \ dp[i][j]=max(dp[i-1][j],dp[i][j-1])

但是复杂度是n^2的,我们还要枚举起始位置,这样复杂度就是n^3了,还是不可以接受,我们再回看问题,发现我们每次a序列只加一个字符,b序列只减少一个字符,而我们DP式子转移的情况在上一次DP里一定能找到,所以不需要n次DP,只要一次就够了。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e6+5;
int dp[5005][5005];
void solve(){
    int n;cin>>n;
    vector<int>a,b;
    a.push_back(0);//只是为了下标从1开始
    for(int i=1;i<=n;++i){
        int x;cin>>x;
        if(x==a.back())continue;//合并一下联通块
        a.push_back(x);
    }
    b.push_back(0);int l=a.size();
    for(int i=l-1;i>=1;--i)b.push_back(a[i]);//b是a的反
    int ans=l-2;
    for(int i=1;i<l-1;++i){//i对应了a现在加了几个字符了
        for(int j=1;j<l-i;++j){//l-i-1是b的字符个数
            if(a[i]==b[j])dp[i][j]=dp[i-1][j-1]+1;//DP的转移
            else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
        ans=min(ans,l-2-dp[i][l-i-1]);//统计答案
    }
    cout<<ans<<'\n';
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

 E. Arithmetic Progression

题意:现在有一个长度为n的数组a,你有60次查询,每次查询,可以选择以下两种操作

(1)问a数组中是否有严格大于x的数,有返回1,没有返回0

(2)问a数组在x位置的数是多少

要在60次查询内找到m和d。

m和d的定义是,把a数组(假设下标从1开始)按从小到大排序,m=a[1],d=a[2]-a[1]。

知识点:二分,随机化?(不是

思路:对于操作(1),我们可以二分找到最大的数组中的元素,设为R,这个操作最多30次,

然后剩下操作,我们随机的在n中选30个位置,他们之中俩俩的差的绝对值一定满足是d的倍数

所以我们把这些数取个gcd,就大概率是d的值了,然后输出R-(n-1)*d 和d(这个证明我还要再想想,具体是多大的概率不是很清楚,不过跑过100多个点,应该挺高的)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e6+5;
bool check(int x){
    cout<<"> "<<x<<endl;
    cout.flush();
    int ans;
    cin>>ans;
    return ans;
}
bool vis[N];
void solve(){
    int n;cin>>n;
    int l=0,r=1e9,mid,R=1e9,cnt=0;
    while(l<=r){//二分找到最大的数组中元素
        mid=l+r>>1;
        cnt++;
        if(check(mid))l=mid+1;
        else r=mid-1,R=mid;
    }
    vector<int>a;
    //随机化
    unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    mt19937 rand_num(seed);  
    uniform_int_distribution<long long> q(1,n);  
    
    for(int i=1;i<=1000000000;++i){
        long long u=q(rand_num);
        if(vis[u])continue;
        vis[u]=true;
        cout<<"? "<<u<<endl;
        cout.flush();
        int x;cin>>x;
        a.push_back(x);
        if(a.size()==n||a.size()+cnt==60)break;
    }
    a.push_back(R);
    int now=0,w=a.size();
    for(int i=0;i<w;++i){
        for(int j=i+1;j<w;++j){
            int w=abs(a[i]-a[j]);//两两差的绝对值
            now=__gcd(now,w);//取个gcd
        }
    }
    cout<<"! "<<R-(n-1)*now<<' '<<now<<endl;//输出答案
}
int main(){
    //ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

F. Please, another Queries on Array?

题意:有一个长度为n的数组a,现在有q次操作,

操作1:把区间[l,r]的每一个a_i都乘x

操作2:询问区间[l,r],\varphi (\prod_{i=l}^{r}a_i)的值,答案对1e9+7取模

知识点:数论,数据结构,bitset

思路:学过一点数论的话,应该知道\varphi (x)=x\times \prod _{i=1}\frac{p_i-1}{p_i},p_i是x分解的质因子

发现a_i \leq 300,我们直接开个bitset每一位代表当前位置这个质数是否在乘积中出现过,然后

区间修改,区间查询,线段树板子题,开bitset是因为空间小,时间也好,不开MLE,然后套套就写完了,线段树主要维护区间乘积和区间中出现的质因子是谁。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=4e5+5;
#define ls (i<<1)
#define rs ((i<<1)|1)
ll a[N];
bitset<305>w[305];//记录每个数字每个位置的质数是否出现过
struct tre{
    bitset<305>p,lazyp;
    ll res,mul,l,r;
}tree[N<<2];
ll ksm(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
//下方类
void work_down(tre &nowtre,ll lazy){
    nowtre.res=nowtre.res*ksm(lazy,nowtre.r-nowtre.l+1)%mod;
    nowtre.mul=nowtre.mul*lazy%mod;
}
//lazy标记
void work_lazy(ll i){
    work_down(tree[ls],tree[i].mul);
    tree[ls].p|=tree[i].lazyp;
    tree[ls].lazyp|=tree[i].lazyp;
    work_down(tree[rs],tree[i].mul);
    tree[rs].p|=tree[i].lazyp;
    tree[rs].lazyp|=tree[i].lazyp;
    tree[i].mul=1;tree[i].lazyp=0;
}
//向上合并
void push_up(tre &nowtre,tre ltre,tre rtre){
    nowtre.p=ltre.p|rtre.p;
    nowtre.res=ltre.res*rtre.res%mod;
}
//解决俩个区间不能直接合并的情况
tre query(ll l,ll r,ll i,ll L,ll R){
    if(L<=l&&r<=R) return tree[i];
    work_lazy(i);
    ll mid=l+r>>1;
    if(mid>=R)return query(l,mid,ls,L,R);
    else if(mid<L)return query(mid+1,r,rs,L,R);
    else {
        tre ltre=query(l,mid,ls,L,R),rtre=query(mid+1,r,rs,L,R),nowtre;
        push_up(nowtre,ltre,rtre);
        return nowtre;
    }
}
//区间修改,带lazy
void change(ll l,ll r,ll i,ll L,ll R,ll d){
    if(l>R||r<L)return ;
    if(L<=l&&r<=R){
        tree[i].p|=w[d];
        tree[i].lazyp|=w[d];
        tree[i].res=tree[i].res*ksm(d,r-l+1)%mod;
        tree[i].mul=tree[i].mul*d%mod;
        return ;
    }
    work_lazy(i);
    ll mid=l+r>>1;
    change(l,mid,ls,L,R,d);
    change(mid+1,r,rs,L,R,d);
    push_up(tree[i],tree[ls],tree[rs]);
}
//建树
void build(ll l,ll r,ll i){
    tree[i].l=l;tree[i].r=r;
    tree[i].mul=1;
    if(l==r){
        tree[i].p=w[a[l]];
        tree[i].res=a[l];
        return ;
    }
    ll mid=l+r>>1;
    build(l,mid,ls);
    build(mid+1,r,rs);
    push_up(tree[i],tree[ls],tree[rs]);
}
ll inv[305];
void solve(){
    inv[1]=1;
    for(int i=2;i<=300;++i){
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;//求个逆元,求欧拉函数用
        int tmp=i;
        for(int j=2;j*j<=tmp;++j){
            if(tmp%j==0){
                w[i][j]=1;
                while(tmp%j==0)tmp/=j;
            }
        }
        if(tmp>1)w[i][tmp]=1;
    }
    int n,q;cin>>n>>q;
    for(int i=1;i<=n;++i)cin>>a[i];
    build(1,n,1);
    while(q--){
        int l,r,x;
        string s;cin>>s;
        if(s[0]=='M'){
            cin>>l>>r>>x;
            change(1,n,1,l,r,x);
        }
        else {
            cin>>l>>r;
            tre ans=query(1,n,1,l,r);
            ll res=ans.res;//欧拉函数的求法
            for(int i=2;i<=300;++i){
                if(ans.p[i])res=res*(i-1)%mod*inv[i]%mod;//定义
            }
            cout<<res<<'\n';
        }
    }
}
int main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0 ;
}

线段树合并一个是按位|,因为只要孩子的俩区间只要出现过,父亲就是有,区间乘积就更简单了,一个快速幂看区间乘了几次就行了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值