Codeforces Round 881 (Div. 3)
E
题目链接
题目大意
给定n个全为0的序列,m个区间l,r (l<=r),定义一个区间是正确的,有且仅当这个区间内的1的数量严格大于0的数量。给定q次修改,每次修改给出一个i,表示将索引i修改为1,求最少需要多少次修改,使得至少有一个区间是正确的。
数据范围
1≤m≤n≤1e5,1≤q≤n
思路
看到这种求答案最值的问题,可以往二分答案这个角度来想,可以发现这个答案具有单调性,所以最重要的就是如何写check()函数。我们发现,每次修改都是从上到下的修改,我们二分答案,即二分修改次数时,可以根据上一次的修改次数进行增或减,即对把少修改的0改为1,多修改的1改为0。查询区间是否是正确的,可以遍历一遍区间数组,对区间求和,判断是否区间的和严格大于区间长度的一半。而这个操作可以通过一个树状数组进行,即单点修改,区间查询。
做法
写一个二分板子,然后写一个check()函数,然后再check()函数中套一个树状数组,进行单点修改和区间查询。
标签
二分、数据结构、树状数组
代码实现
int n, m;
int tree[maxn];
void init() {
for (int i = 1; i <= n; ++i) tree[i] = 0;
}
void add(int i,int x) {
while (i <= n) {
tree[i] += x;
i += lowbit(i);
}
}
int query(int i) {
int res = 0;
while (i > 0) {
res += tree[i];
i -= lowbit(i);
}
return res;
}
void solve() {
cin >> n >> m;
init();
vector<pii>a(m + 1);
for (int i = 1; i <= m; ++i) {
cin >> a[i].first >> a[i].second;
}
int k;
cin >> k;
vector<int>q(k + 1);
for (int i = 1; i <= k; ++i) cin >> q[i];
int pre = 0;
auto check = [&](int x) {
if (pre <= x) {
for (int i = pre+1; i <= x; ++i) { //把少修改的0改为1
add(q[i], 1);
}
}
else {
for (int i = pre; i >= x+1; --i) { //把多修改的1改为0
add(q[i], -1);
}
}
for (int i = 1; i <= m; ++i) { //查询m个区间是否存在符合的情况
if ((query(a[i].second) - query(a[i].first - 1)) * 2 > a[i].second - a[i].first + 1) {
return true;
}
}
return false;
};
int l = 1, r = k, res = -1;
while (l <= r) { //二分答案
int mid = l + r >> 1;
if (check(mid)) {
res = mid;
pre = mid;
r = mid - 1;
}
else {
pre = mid;
l = mid + 1;
}
}
cout << res << endl;
}
时间复杂度
O(m*log2n)
Rating
1600
Codeforces Round 878(Div. 3)
D
题目链接
题目大意
给定n个数的序列a,可以选择任选三个数x,y,z使得MIN(MAX(abs(x-ai),abs(y-ai),abs(z-ai)))最小。
数据范围
1≤n≤2e5,1≤ai≤1e9
思路
看到这种求最值的问题,又仿佛无从下手的题目,不妨试一下二分答案。注意的是,二分答案的情况通常是答案具有单调性,也就是比最终的答案更宽限条件的答案都能实现,比答案更苛刻的条件都无法实现。如果能够判断这一点,那么就可以从这个角度来上手这道题。此外,我们还需要一个check函数来判断答案是否可行。注意的是,check函数一般是要使用贪心的思想,也就是使这个答案最优的情况,如果最优的情况行不通,那么答案一定行不通。
做法
这题的答案显然具有单调性,最差的答案显然是max(a1,a2...an)(当然可以更小,为了简单就设为此),因为可以选x=y=z=0,最好的答案是0,找到了边界,那么我们就可以写一个check函数,来判断答案是否可行。这里可以把数组排序一下,然后枚举每一个点,设x为a1+ans(二分的答案),那么继续判断第二个点距离x的距离,距离大于ans的话就加入第二个点y=(ai+ans),以此类推设置第三个点z,如果设置多余三个点,那么答案肯定是不行的。因为这样的设置已经是最优的设置了,无法更优了
标签
二分、排序,贪心
代码实现
void solve() {
int n;
cin>>n;
vector<int>a(n+1);
int maxx=-inf;
for(int i=1;i<=n;++i){
cin>>a[i];
maxx=MAX(maxx,a[i]); //求右边界
}
sort(a.begin()+1,a.end());//排序
int l=0,r=maxx,res=0;
auto check=[&](int x){//贪心地check
int pre=a[1]+x;
int cnt=1;
for(int i=2;i<=n;++i){
if(abs(pre-a[i])<=x) continue;
else{
++cnt;
pre=a[i]+x;
}
if(cnt>3) return false;
}
return true;
};
while(l<=r){ //二分答案
int mid=l+r>>1;
if(check(mid)){
res=mid;
r=mid-1;
}
else{
l=mid+1;
}
}
cout<<res<<endl;
}
时间复杂度
O(log2(1e9)*n)
Rating
1400