cf1688 F. Sanae and Giant Robot
题意:
给定长都为 n 的数组 \(a[],b[]\),并给定 m 个区间。
每次操作可以从给定区间里选一个区间 \([l,r]\),要求 \(\sum\limits _l^r a_i=\sum\limits _l^r b_i\),然后令 $a_i\gets b_i(l\le i\le r) $
问经过任意次操作后能否使 \(a[]\) 变成 \(b[]\)
\(n,m\le 2e5\)
思路:
令 \(c_i=a_i-b_i\),那么要求就变为 $ \sum\limits _l^r c_i = 0$
记 $sum[] $ 为 \(c[]\) 的前缀和,那么要求就变为 \(sum_{l-1}=sum_r\),操作就变为令 \(sum_{l\sim r}\gets sum_{l-1}\)
目标是使所有 \(sum_i=0\)
所以只能找 \(sum_{l-1}=sum_r=0\) 的区间,令 \(sum_{l\sim r}\gets 0\)。找端点不为 0 的区间是在做无用功
写法是用 queue 记录所有 0 位置进行搜索,每次从 queue 中取一值作为区间的一个端点,若其对应的另一端点也为 0 就把两端点间的值置 0
检查另一端点是不是 0 的方法是开 set 维护所有非 0 位置。用 set 而不是 map 主要是为了快速进行置 0 操作,即只把非 0 位置置 0
const signed N = 5 + 2e5;
int n, m, a[N], b[N];
void sol() {
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) cin >> b[i];
vector<ll> sum(n+1); //ai-bi的前缀和
for(int i = 1; i <= n; i++)
sum[i] = sum[i-1] + a[i] - b[i];
vector<vector<int>> seg(n+1); //区间的另一端点
while(m--) {
int l, r; cin >> l >> r;
seg[l-1].pb(r), seg[r].pb(l-1);
}
set<int> non_zero; //非0位置
for(int i = 0; i <= n; i++)
if(sum[i]) non_zero.insert(i);
queue<int> zero; //sum=0的位置
for(int i = 0; i <= n; i++)
if(!sum[i]) zero.push(i);
while(zero.size()) {
int u = zero.front(); zero.pop();
for(int v : seg[u]) if(!non_zero.count(v)) {
auto [l, r] = minmax(u, v);
auto it = non_zero.upper_bound(l); //把[l,r]置0
while(it != non_zero.end() && *it < r)
zero.push(*it), it = non_zero.erase(it);
}
}
cout << (non_zero.empty() ? "YES\n" : "NO\n");
}