ABC478考试总结

AB 较为简单,不讲

C

简化题意

给定一个长度为 \(n\) 的数组 \(A\),你可以选择一段长度不大于 \(k\) 的区间按升序排序,判断排序后能否使整个数组 \(A\) 升序排列。

题解

  • 前置知识:排序

想要知道排序所需要的区间,我们只需要知道最小和最大的无序位置。因此,用一个 \(b\) 数组表示排序后的 \(A\) 数组。如果 \(b_i \ne A_i\),则这个位置显然是无序的,因为后面有比它小的数。

所以求出第一个无序和最后一个无序的位置 \(l,r\),则需要的长度 \(len\) 就是 \(r-l+1\),判断它和 \(k\) 的大小关系即可。

时间复杂度:\(O(n \log n)\)。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,k,a[N],b[N];
int l,r;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++){cin>>a[i];b[i]=a[i];}
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++){
		if(a[i]!=b[i]){
			if(!l)l=i;
			r=i;
		}
	}
	cout<<(r-l+1<=k ? "Yes" : "No");
	return 0;
}

D

简化题意

给定 \(n\) 个集合,初始均为空集。有 \(q\) 次操作,每次给定 \(x,l,r\),将元素 \(x\) 加入到集合 \(l,l+1,\cdots,r-1,r\) 中。最后询问每个集合内有多少个元素。

题解

  • 前置知识:区间覆盖,差分

由于集合中元素不可重复,因此对于每个不同的 \(x\) 分开处理。记录每个 \(x\) 对应的所有区间 \([l,r]\),然后依次枚举每个不同的 \(x\)。对于同一个 \(x\),先合并有交集的区间,再用差分实现区间加。这样问题就转化为区间覆盖问题,排序后枚举一遍即可,最后通过前缀和得到每个集合的元素个数。

时间复杂度:\(O(q \log q + n)\)。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,q;
map<int,vector<pair<int,int> > >mp;
int diff[N];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>q;
	while(q--){
		int l,r,x;cin>>l>>r>>x;
		mp[x].push_back({l,r});
	}
	for(auto i:mp){
		vector<pair<int,int> >v=i.second;
		sort(v.begin(),v.end());
		int l=v[0].first,r=v[0].second;
		for(int i=1;i<v.size();i++){
			if(v[i].first<=r+1)r=max(r,v[i].second);
			else{
				diff[l]++;diff[r+1]--;
				l=v[i].first;r=v[i].second;
			}
		}
		diff[l]++;diff[r+1]--;
	}
	for(int i=1;i<=n;i++){
		diff[i]+=diff[i-1];
		cout<<diff[i]<<' ';
	} 
	return 0;
}

E

简化题意

给定 \(Q\) 个形如 \(A_{u_i} < A_{v_i}\) 或 \(A_{u_i} \le A_{v_i}\) 的约束条件,问能否生成一个序列,使得满足所有条件且每个数都 \(\le n\)。

题解

  • 前置知识:差分约束,SCC 缩点

观察到这个题目是一个差分约束问题,所以考虑一下差分约束。对于两个约束条件,将其转化为:

  • \(A_{u_i} \le A_{v_i}\)
  • \(A_{u_i}+1 \le A_{v_i}\)

所以对于约束条件 \(1\),我们从 \(u_i\) 到 \(v_i\) 建立一条边权为 \(0\) 的单向边;对于约束条件 \(2\),我们从 \(u_i\) 到 \(v_i\) 建立一条边权为 \(1\) 的单向边。

然后发现不知道怎么做了,尝试一下转化。我们发现对于这张图,约束条件发生冲突当且仅当有两个节点在同一个环中且它们中间那条边的边权为 \(1\),可以参考下图。

image

我们发现这张图需要满足的约束条件为 \(A_3 \ge A_4 \ge A_1 \ge A_2 \ge A_3+1\),是显然矛盾的。

如果把 \(1-2\) 的边也改成 \(1\),则约束条件变为 \(A_3 \ge A_4 \ge A_1 \ge A_2+1 \ge A_3+1\),显然也是矛盾的。

所以我们得出了无解的情况,可以通过 SCC 缩点判环。然后我们发现如果一个环中的边权都是 \(0\),那这个环中的元素不就都是一样的吗?所以我们考虑在拓扑序上 DP,\(dp_i\) 表示当前这个环最少为多少可以满足约束条件。所以按照拓扑序,枚举每一条边 \(u,v,w\),则有转移式:\(dp_u+w \to dp_v\),\(dp_v\) 取所有转移的最大值。

最后判断最长路如果 \(> n\) 则也是无解的,显然不存在让所有元素都 \(\le n\) 的解。

最后每个数的答案就是它所在环的答案。

时间复杂度:\(O(n + q)\)。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,q;
struct edge{
	int v,w;
};
vector<edge>g[N],G[N];
int dfn[N],low[N],cnt;
int st[N],top;
bool inst[N];
int scc[N],tot;
int in[N],dp[N];
void tarjan(int u){
	st[++top]=u;inst[u]=1;
	dfn[u]=low[u]=++cnt;
	for(auto i:g[u]){
		if(!dfn[i.v]){
			tarjan(i.v);
			low[u]=min(low[u],low[i.v]);
		}else if(inst[i.v])low[u]=min(low[u],dfn[i.v]);
	}
	if(dfn[u]==low[u]){
		tot++;
		do{
			inst[st[top]]=0;
			scc[st[top]]=tot;
		}while(st[top--]!=u);
	}
	return;
}
void topu(){
	queue<int>q;
	for(int i=1;i<=tot;i++){
		if(!in[i]){
			q.push(i);
			dp[i]=1;
		}
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(auto i:G[u]){
			dp[i.v]=max(dp[i.v],dp[u]+i.w);
			if(--in[i.v]==0)q.push(i.v);
		}
	}
	return;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>q;
	while(q--){
		int t,u,v;cin>>t>>u>>v;
		g[u].push_back({v,t});
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i])tarjan(i);
	}
	for(int u=1;u<=n;u++){
		for(auto i:g[u]){
			if(scc[u]!=scc[i.v]){
				G[scc[u]].push_back({scc[i.v],i.w});
				in[scc[i.v]]++;
			}else if(i.w){
				cout<<"No";
				return 0;
			}
		}
	}
	topu();
	int maxn=0;
	for(int i=1;i<=tot;i++)maxn=max(maxn,dp[i]);
	if(maxn>n){
		cout<<"No";
		return 0;
	} 
	cout<<"Yes\n";
	for(int i=1;i<=n;i++)cout<<dp[scc[i]]<<' ';
	return 0;
}

F

简化题意

树 \(T\) 有 \(N\) 个点。从 \(1\) 开始,每次取当前候选点中编号最小的点访问,并把它的邻居加入候选,得到访问序列 \(f(T)\)。

给定一个 \(1\sim N\) 的排列 \(Q\),求有多少棵树 \(T\) 满足 \(f(T)=Q\)。

答案对 \(998244353\) 取模。

题解

  • 前置知识:线段树

对于当前的节点 \(Q_i\),设它的父亲为 \(Q_j\)(\(j<i\))。容易发现,对于 \(k \in [j+1,i-1]\),所有的 \(Q_k\) 一定都 \(< Q_i\),否则会与访问顺序矛盾。

设 \(f\) 为满足 \(f<i\) 且 \(Q_f>Q_i\) 的最大下标,那么 \(Q_i\) 的父亲可以是 \([f,i-1]\) 中的任意一个数,对答案的贡献为 \(i-f\)。

这一过程可以用线段树维护,最终答案即为所有贡献的乘积。

时间复杂度:\(O(n \log n)\)。

代码
#include<bits/stdc++.h>
#define int long long
#define ls id<<1
#define rs ls|1
using namespace std;
const int N=200005,mod=998244353;
struct SegmentTree{
	int t[N<<2];
	void pushup(int id){
		t[id]=max(t[ls],t[rs]);
		return;
	}
	void update(int l,int r,int id,int x,int y){
		if(l==r){t[id]=y;return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(l,mid,ls,x,y);
		if(mid<x)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];
		int mid=(l+r)>>1;
		return max(query(l,mid,ls,x,y),query(mid+1,r,rs,x,y));
	}
}pg;
int n;
int q[N]; 
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>q[i];
	if(q[1]!=1){
		cout<<0;
		return 0;
	}
	int ans=1;
	pg.update(1,n,1,q[1],1);
	for(int i=2;i<=n;i++){
		int pos=max(1ll,pg.query(1,n,1,q[i]+1,n));
		ans=ans*(i-pos)%mod;
		pg.update(1,n,1,q[i],i);
	}
	cout<<ans;
	return 0;
}
</details>
posted @ 2026-10-04 07:57  tangkaiming  阅读(9)  评论(0)    收藏  举报