ABC477考试总结
AB 比较简单,不讲。
C
简化题意
给定字符串 \(S,T\),有 \(Q\) 次询问,每次给定 \(L,R\),询问 \(T\) 是否为 \(S_{L,L+1,\cdots,R-1,R}\) 的子串。
题解
- 前置知识:前缀和,字符串
没有修改操作,我们考虑提前求出 \(T\) 在 \(S\) 中出现的每一个位置的开头,这里可以通过内置函数 \(find\) 解决,然后用一个前缀和(记作 \(sum\))求出每个位置之前有多少个 \(T\)。最后在询问时直接判断数量是否大于 \(0\) 即可。因为需要满足 \(T\) 全部位于 \([L,R]\) 中,所以应该是 \(sum_{r-len+1} - sum_{l-1}\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=400005;
int sum[N];
string s,t;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
cin>>s>>t;
int pos=s.find(t);
int len=t.size();
while(pos!=-1){
sum[pos]=1;
pos=s.find(t,pos+1);
}
for(int i=1;i<s.size();i++)sum[i]+=sum[i-1];
while(T--){
int l,r;cin>>l>>r;l--;r--;
if(r-l+1<len){
cout<<"No\n";
continue;
}
int ans=sum[r-len+1];
if(l>0)ans-=sum[l-1];
cout<<(ans ? "Yes\n" : "No\n");
}
return 0;
}
D
简化题意
给定 \(n\) 块初始正面朝上且颜色为 \(a\) 的木板,有两种操作:
- 将编号为 \(x\) 的木板翻面。
- 将所有木板涂成颜色 \(C\)。
最后输出所有木板正面的颜色。
题解
- 前置知识:数组
因为每次只会翻一个木板,所以考虑直接模拟。用 \(now\) 记录最后一次将所有木板涂成的颜色,\(lt\) 记录最后一次涂色的时间,\(f\) 表示上一次被翻成正面的时间,\(vis\) 表示当前是正面还是反面,\(ans\) 记录答案,\(co\) 记录上一次翻成正面时的颜色。
然后依照题目模拟:
如果是翻转操作,分类讨论:
- 当前是正面
判断 \(lt\) 是否大于 \(f\)。若是,则会被染成 \(now\);否则还是 \(co\)。更新 \(vis\)。
- 当前是反面
将 \(co\) 设为 \(ans\),更新 \(f,vis\)。
如果是涂色操作,则修改 \(now\) 和 \(lt\) 即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=300005;
int n,q;
int vis[N];
char ans[N],co[N];//数组意思如上
int f[N];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>q;
char now='a';
int lt=0;
for(int i=1;i<=n;i++)co[i]='a';
for(int t=1;t<=q;t++){
int op;cin>>op;
if(op==1){
int x;cin>>x;
if(!vis[x]){//是正面
if(lt>f[x])ans[x]=now;//在正面的期间被染色了
else ans[x]=co[x];//还没有被染色,还是上次的颜色
vis[x]=1;//更新
}else{
f[x]=t;//记录翻转时间
co[x]=ans[x];//记录上次的颜色
vis[x]=0;//更新
}
}else{
cin>>now;
lt=t;
}
}
for(int i=1;i<=n;i++){//输出答案
if(vis[i])cout<<ans[i];//当前处于翻面状态
else{
if(lt>f[i])cout<<now;//最后的一段时间被染色了
else cout<<co[i];//还是上次的答案
}
}
return 0;
}
E
题解
- 前置知识:单源最短路,前缀和
发现题目给定的图其实就是一个环中间有一个点,如图

所以从一个点到另一个点只有两种方式:
- 经过中间的节点 \(n+1\)
- 走旁边的弧
所以我们分别求出两种路径取最小值即可。
首先是第一种情况,我们求出从节点 \(n+1\) 到其他点的最短路(记为 \(dis\)),那么对于路径 \(s,t\),答案就是 \(dis_s+dis_t\)。
然后是第二种情况,环上的距离更好求,用一个前缀和统计从 \(1\) 出发顺时针走到当前节点的距离,求答案时一减即可得到。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=200005,inf=1000000000000000000;
int n,m;
int a[N],b[N];
struct edge{
int v,w;
};
vector<edge>g[N];
void add_edge(int u,int v,int w){
g[u].push_back({v,w});
g[v].push_back({u,w});
return;
}
int sum[N];
priority_queue<pair<int,int> >q;
int dis[N];
bool vis[N];
void dij(int s){
q.push({0,s});
for(int i=1;i<=n;i++)dis[i]=inf;
while(!q.empty()){
int u=q.top().second;q.pop();
if(vis[u])continue;
vis[u]=1;
for(auto i:g[u]){
if(dis[i.v]>dis[u]+i.w){
dis[i.v]=dis[u]+i.w;
if(!vis[i.v])q.push({-dis[i.v],i.v});
}
}
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
int len=0;
for(int i=1;i<=n;i++){
cin>>a[i];
add_edge(i,i%n+1,a[i]);
sum[i]=sum[i-1]+a[i-1];
len+=a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
add_edge(i,n+1,b[i]);
}
dij(n+1);
while(m--){
int s,t;cin>>s>>t;
if(s==n+1)cout<<dis[t]<<"\n";
else if(t==n+1)cout<<dis[s]<<"\n";
else{
int w=(sum[s]-sum[t]+len)%len;
w=min(w,len-w);//走另一半弧
cout<<min(dis[s]+dis[t],w)<<"\n";
}
}
return 0;
}
F
简化题意
给定一个 \(n\) 行 \(m\) 列的网格,其中第 \(i\) 行的 \([l_i,r_i]\) 被涂成黑色,有 \(q\) 次询问,每次需要你回答一个矩形内有多少个黑色块。
题解
- 前置知识:扫描线,线段树
一个二维数点问题,而且每一层还是连续的区间。
首先对于一个询问,我们用二维前缀和将其拆分成四个询问。
设我们是从左上角 \(x,y\) 到右下角 \(xx,yy\),则二维前缀和:
然后我们将询问按照这些新询问的行排序,从上往下扫。到达行 \(i\) 时,先将区间 \([l_i,r_i]\) 的权值 \(+1\),这里可以使用线段树维护,然后再去处理这里的询问。因为我们已经按照行排序了,所以现在线段树内有的一定是存在于我们的前缀和之中的,我们根据列下标在线段树里查询即可。
一个小细节:因为我们从 \(1\) 开始枚举,所以提前把行为 \(0\) 的询问去掉。
代码
#include<bits/stdc++.h>
#define int long long
#define ls id<<1
#define rs ls|1
using namespace std;
const int N=200005;
int n,m,q;
int l[N],r[N];
struct no{
int id,val,x,y;
}s[N<<2];int tot;
int ans[N];
bool cmp(no u,no v){
return (u.x==v.x ? u.y<v.y : u.x<v.x);
}
int t[N<<2],tag[N<<2];
void pushdown(int l,int r,int id){
int mid=(l+r)>>1;
if(tag[id]){
tag[ls]+=tag[id];tag[rs]+=tag[id];
t[ls]+=(mid-l+1)*tag[id];
t[rs]+=(r-mid)*tag[id];
tag[id]=0;
}
return;
}
void pushup(int id){
t[id]=t[ls]+t[rs];
return;
}
void update(int l,int r,int id,int x,int y){
if(x<=l && r<=y){
t[id]+=r-l+1;
tag[id]++;
return;
}
pushdown(l,r,id);
int mid=(l+r)>>1;
if(x<=mid)update(l,mid,ls,x,y);
if(mid<y)update(mid+1,r,rs,x,y);
pushup(id);
return;
}
int query(int l,int r,int id,int x,int y){
if(r<x || l>y)return 0;
if(x<=l && r<=y)return t[id];
pushdown(l,r,id);
int mid=(l+r)>>1;
return query(l,mid,ls,x,y)+query(mid+1,r,rs,x,y);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=n;i++)cin>>l[i]>>r[i];
for(int i=1;i<=q;i++){
int x,xx,y,yy;cin>>x>>xx>>y>>yy;
s[++tot]={i,1,xx,yy};
s[++tot]={i,-1,xx,y-1};
s[++tot]={i,-1,x-1,yy};
s[++tot]={i,1,x-1,y-1};
}
sort(s+1,s+tot+1,cmp);
int now=1;
while(now<=tot && s[now].x==0)now++;
for(int i=1;i<=n;i++){
update(1,m,1,l[i],r[i]);//将这层的黑色块加入线段树
while(now<=tot && s[now].x==i){
ans[s[now].id]+=query(1,m,1,1,s[now].y)*s[now].val;//查询前缀和,累加
now++;
}
}
for(int i=1;i<=q;i++)cout<<ans[i]<<"\n";
return 0;
}

浙公网安备 33010602011771号