ABC417题解

A. A Substring

简单模拟即可

code
#include<bits/stdc++.h>
using namespace std;
string s;
int n,a,b;
int main(){
	cin >> n >> a >> b;
	cin >> s;
	for(int i = a; i < n-b; ++i){
		cout << s[i];
	}
}

B. Search and Delete

\(B\) 排序后简单模拟即可

code
#include<bits/stdc++.h>
using namespace std;
const int NN = 108;
int n,m;
int a[NN],b[NN];
int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	for(int i = 1; i <= m; ++i) cin >> b[i];
	sort(b+1,b+1+m);
	for(int i = 1,j = 1; i <= n; ++i){
		while(j <= m && b[j] < a[i]) ++j;
		if(a[i] == b[j]){
			++j;
		}
		else{
			cout << a[i] << " ";
		}
	}
} 

C. Distance Indicators

tag: 思维题

这道题,让我们判定 \(j−i=A_i+A_j\) 的对数

我们先分离变量,发现就是找 \(A_i + i = A_j - j\) 的对数

我们从前往后扫,对于每个 \(A_i\),我们在 \(map\) 中查 \(A_i+i\) 并且将结果加入答案,然后我们向 \(map\) 中将 \(A_i-i\) 的出现次数 \(+1\)

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 2e5 + 8;
int n;
ll ans;
int a[NN];
map<int,int> ps,mn;
int main(){
	cin >> n;
	for(int i = 1; i <= n; ++i){
		cin >> a[i];
		ans += ps[i-a[i]];
		ans += mn[a[i]+i];
		++ps[a[i]+i];
		++mn[i-a[i]];
//		cout << ans << endl;
	}
	cout << ans;
	
}

bonus:满足 \(j-i < A_i+A_j\)\((i,j)\) 的对数?

答案

用树状数组即可

D. Takahashi's Expectation

tag:DP

害,线上同步考试的时候没做起,被 \(X_i\leq 10^9\) 卡了半天,结果发现 \(P_i,A_i,B_i\leq 500\)

显然如果心情值一开始大于 \(500\),那么一定会一点一点减少

我们可以用 \(DP\),设 \(f_{i,j}\) 表示若第 \(i\) 个礼物送出去之前还有 \(j\) 的心情值,到最后的心情值大小

\(DP\) 的转移显然是容易的,从最后倒推即可。

而一开始心情值大于 \(500\),我们只需要通过前缀和+二分,就可以找到第一次心情值小于等于 \(500\) 是在哪一次送出礼物之后,然后直接调用 \(DP\) 结果即可求得答案

E. A Path in A Dictionary

tag:贪心 搜索

给一张无向图,然后要求 \(U\to V\) 的字典序最小的路径

我们显然贪心地每一步都走编号更小的点,然后我们再加一个记忆化记录从一个点 \(x\) 出发,不能走回上一个来时的点 \(fa\) 能不能走到 \(V\) 即可,然后搜到的第一条路径就是答案

至于证明,显然先走向编号更大的点找到的路径字典序一定比走向编号更小的点的字典序更大

这样记忆化之后,我们保证了每条边只会走一次,复杂度 \(O(\sum m)\)

code
#include<bits/stdc++.h>
using namespace std;
const int NN = 1e3 + 8;

vector<int> e[NN];
vector<int> sta;
bool fm[NN][NN];
bool vis[NN],gt;
void dfs(int u,int t,int fa){
	if(fm[fa][u]) return;
	sta.push_back(u);vis[u] = 1;
	if(u == t){
		gt = 1;return;
	}
	for(int i = 0; i < e[u].size(); ++i){
		int v = e[u][i];
		if(vis[v]) continue;
		dfs(v,t,u);
		if(gt) return;
		else fm[u][v] = 1;
	}
	sta.pop_back();vis[u] = 0;
}
int T,n,m,s,t;
int main(){
	cin >> T;
	while(T--){
		sta.clear();
		cin >> n >> m >> s >> t;
		gt = 0;
		for(int i = 1; i <= n; ++i) e[i].clear(),vis[i] = 0;
		for(int i = 1,u,v; i <= m; ++i){
			cin >> u >> v;
			e[u].push_back(v);
			e[v].push_back(u);
		}
		for(int i = 1; i <= n; ++i)
			sort(e[i].begin(),e[i].end());
		dfs(s,t,s);
		for(int i = 0; i < sta.size(); ++i){
			cout << sta[i] << ' ';
		}
		for(int i = 1; i <= n; ++i){
			for(int j = 0; j < e[i].size(); ++j)
				fm[i][e[i][j]] = 0;
		}
		cout << '\n';
	}
}

F. Random Gathering

tag:期望 线段树

去掉期望的外壳之后就是一道 区间平均的题

我们显然对于区间平均操作,可以用一个区间查询 + 区间赋值操作平替

这样之后,接下来的就是线段树的板子了

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
const int NN = 2e5 + 8;

int n,m;
ll a[NN];

inline ll ksm(ll x,ll k){
	ll ans = 1;
	while(k){
		if(k & 1) ans = ans * x % MOD;
		x = x * x % MOD;
		k >>= 1;
	}
	return ans;
}
inline ll inv(ll x){
	return ksm(x, MOD - 2);
}

struct Seg{
	int l,r;
	ll sum,tag;
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define ls(x) (x << 1)
	#define rs(x) (x << 1 | 1)
	#define sum(x) tree[x].sum
	#define tag(x) tree[x].tag
}tree[NN << 2];
void addlz(int x,ll num){
	sum(x) = (r(x) - l(x) + 1) * num % MOD;
	tag(x) = num;
}
void pushdown(int x){
	if(tag(x) != -1) addlz(ls(x),tag(x)), addlz(rs(x),tag(x));
	tag(x) = -1;
}
void pushup(int x){
	sum(x) = sum(ls(x)) + sum(rs(x));
	if(sum(x) >= MOD) sum(x) -= MOD;
}
void build(int x,int l,int r){
	l(x) = l;r(x) = r;
	if(l == r){
		addlz(x,a[l]);return;
	}
	tag(x) = -1;
	int mid = (l + r) / 2;
	build(ls(x),l,mid);
	build(rs(x),mid+1,r);
	pushup(x); 
}
ll query(int x,int l,int r){
	if(l <= l(x) && r(x) <= r){
		return sum(x);
	}
	pushdown(x);
	ll ans = 0,mid = (l(x) + r(x)) / 2;
	if(l <= mid) ans += query(ls(x),l,r);
	if(mid + 1 <= r) ans += query(rs(x),l,r);
	return ans % MOD;
}
void modify(int x,int l,int r,int num){
	if(l <= l(x) && r(x) <= r){
		addlz(x,num);
		return;
	}
	int mid = (l(x) + r(x)) / 2;
	pushdown(x);
	if(l <= mid) modify(ls(x),l,r,num);
	if(mid + 1 <= r) modify(rs(x),l,r,num);
	pushup(x);
	return;
}
void print(int x){
	if(l(x) == r(x)){
		printf("%lld ",sum(x));
		return;
	}
	pushdown(x);
	print(ls(x)); print(rs(x));
	return;
}

int main(){
	ios::sync_with_stdio(false),cin.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; ++i){
		cin >> a[i];
	}
	build(1,1,n);
	for(int i = 1,l,r; i <= m; ++i){
		cin >> l >> r;
		ll num = query(1,l,r) * inv(r-l+1) % MOD;
//		printf("%lld\n",num);
		modify(1,l,r,num);
	}
	print(1);
	return 0;
}

G. Binary Cat

tag:重链剖分

想象一个有向无环图(DAG),包含 \(Q+2\) 个节点(编号 \(0\)\(Q+1\))。节点 \(0\)\(1\) 是终止节点,其余每个节点都有一个"左父节点"和一个"右父节点"。

指向左父节点的边权重为 \(0\),指向右父节点的边权重等于左父节点对应字符串的长度。

朴素解法时间复杂度为 O(\(Q^2\)):处理查询时,根据索引 \(X_i\) 所属的父节点(左或右)向上回溯,并相应调整索引位置,直至抵达终止节点。

如何优化?虽然可以考虑倍增,但由于存在两个父节点(而非单一父节点),直接应用较为困难。

关键在于:不区分左/右父节点,而是将对应字符串较短的父节点定义为"轻父节点",另一个为"重父节点"(利用边权重区分左右——指向左父节点的边权重恒为 0)。

当通过轻父节点回溯时,字符串长度至少减半;而通过重父节点回溯时,可利用倍增优化重链的爬升过程。(类似树链剖分)

因此,处理每次查询时:

最多经历 \(O(\log M)\) 次轻边回溯

每次重边链的倍增耗时 \(O(\log Q)\)
总时间复杂度为 \(O(Q\cdot\log M\cdot\log Q)\)

posted @ 2025-08-05 23:41  ricky_lin  阅读(44)  评论(0)    收藏  举报