题解:P10837 『FLA - I』云音泛
你说得对,但是就是喜欢用离散化。
题目可以简化为 \(n\) 条长度一定线段覆盖,问只被一条线段覆盖的点的个数。
由于只能移动一条线段,所以我们可以计算出每一条线段移动最多能够增加的贡献,然后取最大的。
首先对于一条线段,由于其可以任意移动,那么最有效的方式自然是让它覆盖一个未被覆盖的区间,这样移动后的答案是最大的。
考虑移动第 \(i\) 条线段对答案产生的影响。首先这条线段原来覆盖的区域覆盖数要减一。记区间内移动前被两条线段覆盖的点数为 \(cnt2_i\),移动前被一条线段覆盖的点数为 \(cnt1_i\),则答案减少 \(cnt1_i\),增加 \(cnt2_i\)。
同时还有移动后,这条线段覆盖了一个原本未被覆盖的区间,所以答案还要加上线段长度 \(m\)。
总结一下,答案变为
\[ans - cnt1_i + cnt2_i + m
\]
取最大值即可。
计算某个点上有多少条线段覆盖,可以用树状数组区间修改,单点查询。当然线段树也可以。但是谁会拒绝一个又好写又常数小的可爱的树状数组呢?
但是我们发现这样做理论上树状数组应该开到 \(t_i + m\),这样是开不下的。
于是我们使用传统艺能,离散化。
但是离散化不能只记录线段的两个端点,例如下面这个例子:两条线段分别覆盖区间 \([2,5]\) 和 \([7,10]\)。
离散化后我们记录的值是:\(1,1,1,1\)。
我们将 \([5,7]\) 也认为是有线段覆盖的了。
因此我们需要一个标记,作为左右边界的标记。一个简单的办法就是将 \(l - 1\) 和 \(r + 1\) 也放入离散化中。这样离散化后,上例的答案就变为:\(0,1,1,0,0,1,1,0\),将两个不连续的区间分开来了。
具体实现见代码:
#include<bits/stdc++.h>
#define MAXN 400010
using namespace std;
typedef long long ll;
ll n, m, tot;
ll lt[MAXN], rt[MAXN];
ll sum[MAXN << 2], t[MAXN << 2], cnt1[MAXN << 2], cnt2[MAXN << 2], res[MAXN << 2];
ll low_bit(ll x){ return x & (-x); }
void add(ll i, ll k){
while(i <= tot){
sum[i] += k;
i += low_bit(i);
}
}
ll query(ll i){
ll res = 0;
while(i > 0){
res += sum[i];
i -= low_bit(i);
}
return res;
}
void update(ll l, ll r, ll x){
add(l, x);
add(r + 1, -x);
}
int main(){
scanf("%lld%lld",&n,&m);
for(ll i = 1; i <= n; i++) scanf("%lld",<[i]);
for(ll i = 1; i <= n; i++) rt[i] = lt[i] + m - 1;
for(ll i = 1; i <= n; i++){
t[++tot] = lt[i] - 1;
t[++tot] = lt[i];
t[++tot] = rt[i];
t[++tot] = rt[i] + 1;
}
sort(t + 1, t + tot + 1);
tot = unique(t + 1, t + tot + 1) - t - 1;
for(ll i = 1; i <= n; i++) lt[i] = lower_bound(t + 1, t + tot + 1, lt[i]) - t;
for(ll i = 1; i <= n; i++) rt[i] = lower_bound(t + 1, t + tot + 1, rt[i]) - t;
for(ll i = 1; i <= n; i++){
update(lt[i], rt[i], 1);
}
for(ll i = 1; i <= tot; i++) res[i] = query(i);
for(ll i = 2; i <= tot; i++){
if(res[i] == 1 && res[i - 1] >= 1) cnt1[i] = t[i] - t[i - 1];
else if(res[i] == 1 && res[i - 1] < 1) cnt1[i] = 1;
else if(res[i - 1] == 1 && res[i] >= 1) cnt1[i] = t[i] - t[i - 1] - 1;
}
for(ll i = 2; i <= tot; i++){
if(res[i] == 2 && res[i - 1] >= 2) cnt2[i] = t[i] - t[i - 1];
else if(res[i] == 2 && res[i - 1] < 2) cnt2[i] = 1;
else if(res[i - 1] == 2 && res[i] >= 2) cnt2[i] = t[i] - t[i - 1] - 1;
}
ll ans = 0;
for(ll i = 1; i <= tot; i++) ans += cnt1[i];
for(ll i = 1; i <= tot; i++) cnt1[i] += cnt1[i - 1];
for(ll i = 1; i <= tot; i++) cnt2[i] += cnt2[i - 1];
ll maxx = 0;
for(ll i = 1; i <= n; i++){
ll c1 = cnt1[rt[i]] - cnt1[lt[i] - 1], c2 = cnt2[rt[i]] - cnt2[lt[i] - 1];
ll tmp = c2 + m - c1;
maxx = max(maxx, tmp);
}
printf("%lld\n",ans + maxx);
return 0;
}
记得开 long long
。