Codeforces Round #707 (Div. 2, based on Moscow Open Olympiad in Informatics)A-D题解

Codeforces Round #707 (Div. 2, based on Moscow Open Olympiad in Informatics)A-D题解
比赛链接:https://codeforces.com/contest/1501/problem/A

A题
相关tag:阅读理解,模拟

题意比较绕,多逼着自己只查单词不整篇翻译,读题能力就上来了。

题意:
从时间0开始出发,要依次经过在一条直线上的n个站点,问到达站点n的时间。
每个站点i都有对应的时间a[i],b[i],t[i]
从站点i-1到站点i的路上需要花费时间a[i]-b[i-1]+t[i]
到达站点i后,从站点i出发到下个站点需要满足两个条件:
1.在站点i至少需要等待(b[i]-a[i])/2(向上取整)的时间
2.出发的时间不能在b[i]时间前

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e2+7;
const double eps=1e-6;
const int mod=1e9+7;

int a[maxn],b[maxn],T[maxn];
int n;

int main(){
    IOS
    int t;cin>>t;
    while(t--){
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
        for(int i=1;i<=n;i++) cin>>T[i];
        int now=0;//now代表当前时间
        for(int i=1;i<n;i++){//这里走到站点n-1即可
            now+=a[i]-b[i-1]+T[i];//从站点i-1到站点i需要划分的时间
            int temp=(b[i]-a[i]+1)/2;//在站点i至少需要等待的时间
            now+=temp;
            if(now<b[i]) now=b[i];//从站点i离开必须到时间b[i]之后
        }
        now+=a[n]-b[n-1]+T[n];//到达站点n就可以立即结束,不需要再多加时间
        cout<<now<<endl;
    }
}

B题
相关tag:逆向思维,模拟

题意:
一层一层依次堆叠n层蛋糕,堆叠第i层蛋糕时,会把从顶部往下的a[i]层蛋糕染色。
问最后的染色情况。

思路:
反过来思考,从顶向下看,用now记录当前向下会有多少层被染色,这样就避免了暴力对同一层重复染色的问题。
当然也可以差分来写。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;

int a[maxn];
bool flag[maxn];//flag标记是否被染色

int main(){
    IOS
    int t;cin>>t;
    while(t--){
        int n;cin>>n;
        for(int i=0;i<n;i++) cin>>a[i];

        int now=0;//now记录从当前位置向下有多少层会被染色
        for(int i=n-1;i>=0;i--){
            now=max(now,a[i]);//更新更大的now值
            if(now) {flag[i]=1;now--;}//如果now不为0,将当前层染色
        }

        for(int i=0;i<n;i++){
            cout<<flag[i]<<' ';
            flag[i]=0;
        }
        cout<<endl;
    }
}

C题
相关tag:暴力,数据范围分析

题意:
有n个数,问能否从中挑选出四个数字,使得其中两个相加等于另两个相加。

思路:
注意到这道题的数字范围最大为2.5e6,那么相加得到的最大值就是5e6。
那么我们暴力枚举所有下标对,记录下相加等于i的下标对有哪些。
对于每个和x,我们平均至多枚举3-4个下标对就必然能够得到答案,找到答案后立刻break,因此复杂度会在1e7的级别完全足够。
暴力实现即可。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=2e5+7;
const double eps=1e-6;
const int mod=1e9+7;

int a[maxn];
bool cas[5000007];
vector<pair<int,int>>ans[5000007];//ans[i]记录和为i的下标对有哪些

int main(){
    IOS
    int n;cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=2;i<=n;i++){
        for(int j=1;j<i;j++){//暴力枚举
            int sum=a[i]+a[j];
            for(int k=0;k<ans[sum].size();k++){//暴力检测之前已经得到的相加等于sum的下标对
                if(ans[sum][k].first!=i&&ans[sum][k].second!=i&&ans[sum][k].first!=j&&ans[sum][k].second!=j){
                    //如果满足四个下标均不相同代表找到了答案
                    cout<<"YES"<<endl;
                    cout<<ans[sum][k].first<<' '<<ans[sum][k].second<<' '<<j<<' '<<i<<endl;
                    return 0;
                }
            }
            ans[sum].push_back({j,i});
        }
    }
    cout<<"NO"<<endl;
}

D题
扩展欧几里得,二分

题意:
给定一个长度为n的数组a和一个长度m的数组b,已经一个值k。
对这两个数组不断循环扩展,问这两个数组第k个数值不相同的下标位置。

思路:
这里首先需要推出一个小结论,
对于长度n和m来说,他们的最大公约数为g,那么n和m可以看成若干段长度为g的部分连接而成。
长度为g的部分是这两个数组的最小单位。

如果a[i](1<=i<=n)与b[j](1<=j<=m)在循环扩展后的同一个下标出现的话,i和j需要满足i%g==j%g。
注意到题目说了每个数组中的数字都是两两不同的,且数值在1e6之内。
那么我们可以用flag[x]记录数值x在数组a中出现的位置下标,在for一遍b数组,检测b中的每个数字在a中出现的下标位置,是否满足上述要求,如果满足的话,在一个lcm(最小公倍数)长度的循环中,会出现一次下标相同的情况,就是1次数值相同的情况。
由此我们可以计算出,一次lcm长度,会有多少对下标的数值是相同的,也就可以求出多少对下标的数值是不同的,记作L。

但是k不一定能被L整除,会有多余的部分需要继续在一轮lcm里找到具体的位置。
如果我们可以求出上一段叙述中,那些下标相同的具体位置在哪里,记录后利用二分就可以在log(n)的时间得到具体位置。
注意到如果a[i]与b[j]存在一个下标相同的位置,那么我们实际上是可以写出x × \times ×n+i=y × \times ×m+j的式子。
移项后得到x × \times ×n+(-y) × \times ×m=j-i,且j-i必定是g的整数倍。
那么系数x和y我们可以通过扩展欧几里得来求得。

基本思路完毕。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f //2139062143
#define llINF 9223372036854775807
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=5e5+7;
const double eps=1e-6;
const int mod=1e9+7;

ll exgcd(ll a,ll b,ll &x,ll &y){
    if(!a&&!b) return -1;
    if(!b)
    {
        x=1;y=0;
        return a;
    }
    ll ret=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return ret;
}

ll n,m,k;
ll numa[maxn],numb[maxn];
ll flag[maxn*2];
ll lm;//n和m的最小公倍数
vector<ll>cas;//记录一次lcm的长度内,有哪些下标是字母相同的

ll check(ll x){//单次lcm长度内,第x不匹配数字出现的情况
    if(x==0){//特殊处理恰好整数组lcm长度的情况
        ll ret=lm;
        for(int i=0;i<cas.size();i++){
            if(cas[cas.size()-i-1]==lm-i){
                ret--;
            }
            else break;
        }
        return ret-lm;
    }
    ll l=0,r=(ll)cas.size()-1;//二分查找最后一个匹配的位置,借由此计算出最后多余的长度是多少
    while(l<r){
        int mid=((l+r)>>1)+1;
        if(cas[mid]-mid>=x) r=mid-1;
        else l=mid;
    }
    return x+l;
}

int main(){
    scanf("%lld%lld%lld",&n,&m,&k);
    ll x,y;
    ll g=exgcd(n,m,x,y);//n和m的最大公约数,并计算出x*n+y*m=g的一组特解x,y
    lm=n/g*m;
    for(int i=1;i<=n;i++){
        scanf("%lld",&numa[i]);
        flag[numa[i]]=i;
    }
    for(int i=1;i<=m;i++){
        scanf("%lld",&numb[i]);
        if(flag[numb[i]]&&flag[numb[i]]%g==i%g){//需要满足字母相同,且对最大公约数取模后的值相等,才可能存在满足x*n+y*m=距离的解
            if(flag[numb[i]]==i) cas.push_back(i);
            else{
                //ll tempx=x*(i-flag[numb[i]])/g;
                ll tempy=y*((i-flag[numb[i]])/g)%lm;//求出当前情况对应的特解
                ll tar=-tempy*m+i;//计算下标位置,下标应该在1-lcm之内,因此下面对其值进行处理
                if(tar<0){
                    tar+=((-tar)/lm+1)*lm;
                }
                tar%=lm;
                if(tar==0) tar=lm;
                cas.push_back(tar);
            }
        }
    }
    cas.push_back(0);
    sort(cas.begin(),cas.end());
    ll ans=k/(lm-(ll)cas.size()+1)*lm;//取多少整数组长度的lcm
    k-=k/(lm-(ll)cas.size()+1)*(lm-(ll)cas.size()+1);//剩余还需要几次不相同
    printf("%lld\n",ans+check(k));
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值