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

题解

  • 前置知识:单源最短路,前缀和

发现题目给定的图其实就是一个环中间有一个点,如图

image

所以从一个点到另一个点只有两种方式:

  • 经过中间的节点 \(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\),则二维前缀和:

\[sum_{xx,yy} - sum_{xx,y-1} - sum_{x-1,y} + sum_{x-1,y-1} \]

然后我们将询问按照这些新询问的行排序,从上往下扫。到达行 \(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;
}
posted @ 2026-09-27 15:04  tangkaiming  阅读(12)  评论(0)    收藏  举报