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));
}