AtCoder Beginner Contest 428(ABCDEF)
前言
你管这叫beginner contest???
一、A - Grandma's Footsteps
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int s,a,b,x;cin>>s>>a>>b>>x;int round=x/(a+b);int sum=round*a*s;int nxt=x%(a+b);if(nxt<a){cout<<sum+s*nxt<<endl;}else{cout<<sum+s*a<<endl;}
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
首先因为走a秒歇b秒,所以一轮的总时间就是a+b秒,那么就可以求出一共可以走几轮。同时,走完之后肯定还会剩几秒,那么再特判一下剩的这几秒都在走还是走完了歇一会即可。
二、B - Most Frequent Substrings
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int n,k;cin>>n>>k;string s;cin>>s;s=" "+s;map<string,int>cnts;for(int i=1;i<=n-k+1;i++){cnts[s.substr(i,k)]++;}int ans=0;for(auto [s,c]:cnts){ans=max(ans,c);}cout<<ans<<endl;for(auto [s,c]:cnts){if(c==ans){cout<<s<<" ";}}cout<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这个题直接暴力即可,建立一个滑动窗口,把所有长度为k的子串都收集起来,然后先找出最大词频,然后再把这个词频对应的子串全输出即可。
三、C - Brackets Stack Query
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;void solve()
{int q;cin>>q;vector<int>sum={0};int low=0;int op;char ch;while(q--){cin>>op;if(op==1){cin>>ch;if(ch=='('){sum.push_back(sum.back()+1);}else{sum.push_back(sum.back()-1);if(low==0&&sum.back()<0){low=sum.size();}}}else{sum.pop_back();if(sum.size()<low){low=0;}}if(low==0&&sum.back()==0){cout<<"Yes"<<endl;}else{cout<<"No"<<endl;}}
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这个题就涉及到括号匹配的知识了。首先,对于能匹配的括号串,将左括号看作+1,右括号看作-1。那么若一个括号串能匹配,就说明按上述方式所求累加和等于0,且中间每一个位置的前缀和都大于等于0。所以考虑用一个sum数组存每一个位置的前缀和,再用一个low变量记录第一个前缀和小于0的位置。那么对于每一个右括号,都判断一下若low没有设置且当前的前缀和小于0了,就把low设置成当前sum数组的长度。那么之后每次删末尾就是让sum数组pop_back一下,然后若长度小于low了,就说明此时每个位置又满足了前缀和大于等于0,所以把low设为0。所以最后只需要判断一个sum数组的最后一位是否是0且low是否是0即可。
四、D - 183184
讨厌数学题……
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;//求1~x范围上的平方数的个数
//sqrt会有精度问题
ll cal(ll x)
{ll res=sqrt(x);//手动修正while(res*res>x){res--;}while((res+1)*(res+1)<=x){res++;}return res;
}void solve()
{ll c,d;cin>>c>>d;//若C+x有d位,那么拼接的结果就是C*10^d+C+x//所以可以考虑去枚举C+x的位数d,有10^(d-1)<=C+x<=10^d-1//整理得10^(d-1)-C<=x<=10^d-C-1//又因为1<=x<=D,所以可以有左边界L=max(1,10^(d-1)-C),右边界R=min(D,10^d-C-1)//又因为f(C,C+x)=C*10^d+C+x,就有C*10^d+C+L<=f(C,C+x)<=C*10^d+C+R//所以就转化成了求这个区间内有多少个平方数ll ans=0;//i:当前是10的i次方//j:10的i次方for(ll i=1,j=10;i<=10;i++,j*=10){ll L=max(1ll,j/10-c);ll R=min(d,j-c-1);if(L>R){continue;}ans+=cal(j*c+c+R)-cal(j*c+c+L-1); }cout<<ans<<endl;
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;cin>>t;while(t--){solve(); }return 0;
}
首先观察到若C+x有d位,那么拼接后的结果就是C*10的d次方+C+x,所以可以考虑去枚举C+x的位数d。那么就有10的d-1次方小于等于C+x小于等于10的d次方-1,,所以移项后就有10的d-1次方-C小于等于x小于等于10的d次方-1-C。又因为1<=x<=D,所以左边界L就等于10的d-1次方-C和1的最小值,右边界R就等于10的d次方-1-C和D的最小值。又因为f(C,C+x)等于C*10的d次方+C+x,所以就有C*10的d次方+C+L小于等于f(C,C+x)小于等于C*10的d次方+C+R,那么问题就转化成了求这个区间内有多少个平方数。
注意sqrt面对long long会有精度问题,所以要手动修正一下。
五、E - Farthest Vertex
byd马上要看左老师树的直径这一节,结果正好考了这么一个题,哪里不会考哪里了属于是……
先空着,我看完课立马补!()
六、F - Pyramid Alignment
你已经学会了二分,快来做做F吧!(疯)
#include <bits/stdc++.h>
using namespace std;/* /\_/\
* (= ._.)
* / > \>
*/typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;const int MAXN=2e5+5;vector<ll>a(MAXN);//连续段组
vector<array<ll,4>>seg;//返回v这个线段当前的左右端点
pll query(array<ll,4>&cur,ll v)
{if(cur[3]==0){return {cur[2],cur[2]+a[v]};}else{return {cur[2]-a[v],cur[2]};}
}//二分搜索编号为v的区间的左右端点
pll find(ll v)
{int l=0;int r=seg.size()-1;int m;int ans=-1;while(l<=r){m=l+r>>1;if(seg[m][1]>=v){ans=m;l=m+1;}else{r=m-1;}}return query(seg[ans],v);
}//二分搜索编号为v的区间所在的连续段
int findSeg(ll v)
{int l=0;int r=seg.size()-1;int m;int ans=-1;while(l<=r){m=l+r>>1;//当前连续段编号最大的区间的左右端点auto cur=query(seg[m],seg[m][1]);//全包 -> 左闭右开if(cur.first<=v&&v<cur.second){ans=m;l=m+1;}else{r=m-1;}}return ans;
}//在v所在连续段中二分搜索最小包含v的区间
int findV(int l,int r,int pos,ll v)
{int m;int ans=-1;while(l<=r){m=l+r>>1;auto cur=query(seg[pos],m);//全包 -> 左闭右开if(cur.first<=v&&v<cur.second){ans=m;r=m-1;} else{l=m+1;}}return ans;
}void solve()
{int n;cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}//不管是操作1还是操作2,每次都会生成一个左端点或右端点相同的连续段,所以考虑以这个连续段为单位维护线段//定义四元组(l,r,x,t)为编号区间[l,r]中的所有端点的(t==0?左:右)端点全部移动到了x,然后对其按r从小到大排序//对于操作1和2,每次操作时,可以二分搜索v所在的连续段,找出连续段的L和R//之后从小到大遍历,暴力考虑所有连续段u//若Ru<=v,那么直接删除这个连续段,否则就替换Lu=v+1,插入(1,v,Lv/Rv,0/1),结束遍历//由于至多插入q次,所以删除也至多q次,所以均摊下来只会修改O(q)次连续段//对于操作三,因为无论如何操作,大的区间必然包含了小的区间//所以只需要找到第一个包含x+1/2的线段p,答案为n-p+1//那么就是第一次二分找出该连续段,第二次二分在该连续段内二分//整体时间复杂度O(nlogn)//vector方便在末尾push和pop,所以倒序存入for(int i=n;i>=1;i--){seg.push_back({i,i,a[i],1});}int q;cin>>q;int op,v;while(q--){cin>>op>>v;if(op<=2){auto info=find(v);while(!seg.empty()){auto last=seg.size()-1;if(seg[last][1]<=v){seg.pop_back();}else{seg[last][0]=v+1;break;}}if(op==1){seg.push_back({1,v,info.first,0});}else{seg.push_back({1,v,info.second,1});}}else{int pos=findSeg(v); if(pos==-1){cout<<0<<endl;continue;}pos=findV(seg[pos][0],seg[pos][1],pos,v);if(pos==-1){cout<<0<<endl;continue;}cout<<n-pos+1<<endl;}}
}signed main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int t=1;//cin>>t;while(t--){solve(); }return 0;
}
这题补的时候真的一眼二分,但想了半天没想出来怎么二分,没想到题解居然这么复杂……
首先,不管是操作1还是操作2,每次都会生成一个左端点或右端点相同的连续段,所以考虑以这个连续段为单位维护每个线段。所以考虑定义一个四元组(l,r,x,t),表示编号从l到r的线段,当前(t==0?左:右)端点都在x处,然后对其按r从小到大排序。之后,不管对于操作1还是操作2,每次操作时,可以二分搜索v所在的连续段,找出v这个线段当前的L和R。之后就可以暴力从小到大遍历每个连续段,若当前连续段的r小于v,那么就可以直接删除,否则就替换当前连续段的l为v+1,然后插入(1,v,L/R,0/1)。而对于操作3,因为注意到无论如何操作,大的区间一定包含了小的区间,所以只需要找到包含x+1/2的最小区间即可,所以可以考虑第一次二分找出x+1/2所在的连续段,再在这个连续段内二分找出最小区间即可。
总结
我感觉这套题放div2都够了……
