Gym102001 - Lexical Sign Sequence(贪心,思维)

题目

Problem - H - Codeforces

给定一个整数序列\(\{a_i\}\)满足\(-1\le a_i\le 1\),给定\(k\)个约束条件\((l,r,d)\)代表\(\sum\limits_{i=l}^{r}{a_i}\ge d\)。你需要将\(\{a_i\}\)中所有为0的位置的值替换为-1或1,使得最终序列满足所有约束条件且字典序最小,无解输出impossible

题解

方法1

先将所有为0的位置用-1填充,然后按照约束区间右端点从小到大排序。然后遍历约束区间从右到左将可修改位置贪心地将-1改为1,直到序列满足当前约束区间的条件。用数状数组维护区间和,set维护可修改位置,每次修改一个位置后将其从set中删去。

正确性:为了字典序最小,一开始用-1填充,然后按从右到左的顺序将尽可能少的-1改为1使得满足约束。为了修改尽可能少的-1,就要选择约束区间重叠最多位置。按照约束区间右端点从小到大排序后,从右到左贪心选择修改位置,可以保证选择的位置重叠的区间最多。而且每个约束区间都要满足,这样贪心地选一定是最优的。

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 3e5 + 10;
const double eps = 1e-5;

int tarr[N];
int n, k;

int lowbit(int x) {
    return x&-x;
}

void add(int p, int val) {
    while(p <= n) {
        tarr[p] += val;
        p += lowbit(p);
    }
}

int sum(int p) {
    int res = 0;
    while(p) {
        res += tarr[p];
        p -= lowbit(p);
    }
    return res;
}

set<int> pos;

struct node {
    int l, r, d;
    bool operator<(const node& rhs) const {
        if(r == rhs.r) return l < rhs.l;
        return r < rhs.r;
    }
};

node seg[N];
int arr[N];

int main() {
    IOS;
    cin >> n >> k;
    for(int i = 1; i <= n; i++) {
        cin >> arr[i];
        if(arr[i] == 0) {
            pos.insert(i);
            arr[i] = -1;
        }
        add(i, arr[i]);
    }
    for(int i = 1; i <= k; i++) {
        int l, r, d;
        cin >> l >> r >> d;
        seg[i] = node{l, r, d};
    }
    sort(seg + 1, seg + 1 + k);
    bool ok = true;
    for(int i = 1; i <= k; i++) {
        int l = seg[i].l, r = seg[i].r, d = seg[i].d;
        d = sum(r) - sum(l - 1) - d;
        while(d < 0) {
            auto it = pos.upper_bound(r);
            if(it == pos.begin()) {
                ok = false;
                break;
            }
            it--;
            if(*it < l) {
                ok = false;
                break;
            }
            arr[*it] = 1;
            add(*it, 2);
            d += 2;
            pos.erase(it);
        }
    }
    if(!ok) cout << "Impossible" << endl;
    else {
        for(int i = 1; i <= n; i++) cout << arr[i] << " \n"[i == n];
    }
}

方法2

按照常规的思路想就是,一开始将0填充为1,然后从左往右遍历可修改位置,判断此时修改成-1是否还满足所有约束条件,如果满足就贪心地改成-1;否则遍历下一个可修改位置。这样的正确性是显然的,但暴力会超时。

考虑优化。对于约束条件\((l,r,d)\),其等价形式就是区间\((l,r)\)内最多有\(\lfloor\frac{r-l+1-d}{2}\rfloor\)个-1。

因此可以维护一个数据结构,当遍历到位置\(i\),将左端点的为\(i\)的区间以其可用的-1数量插入数据结构,然后查询当前位置\(i\)如果变为-1,数据结构中最少的可用的-1数量是否大于0。如果发生修改,就对将数据结构中所有的可用的-1数量均减去1。最后再将右端点为\(i\)的区间全部弹出。然后处理下一个位置。

显然,用这种方法,任意时刻到达位置\(i\),数据结构中就含有所有包含位置\(i\)的区间。如果要支持修改,会很难实现,所以可以反过来,所有减去1,相当于其他加上1。维护一个\(cnt\)代表当前修改了多少-1,然后插入\(\lfloor\frac{r-l+1-d}{2}\rfloor+cnt\),查询时和当前\(cnt\)比较即可。这样就只有插入查询和删除,可以用set实现。思想类似有一题聊天室的题,用差分代替每次修改。

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 3e5 + 10;
const double eps = 1e-5;



struct node {
    int l, r, d;
};
typedef pair<int, int> PII; 
node seg[N];
int arr[N];
vector<int> segl[N], segr[N];
set<PII> segs;
int limit[N];
int pre[N];
int precnt[N];

bool check(int n, int k) {
    for(int i = 1; i <= n; i++) pre[i] = pre[i - 1] + arr[i];    
    for(int i = 1; i <= k; i++) {
        int l = seg[i].l, r = seg[i].r, d = seg[i].d;
        if(pre[r] - pre[l - 1] < d) return false;
    }
    return true;
}

int main() {
    IOS;
    int n, k;
    cin >> n >> k;
    for(int i = 1; i <= n; i++) {
        cin >> arr[i];
        if(arr[i] == -1) precnt[i] = 1;
        precnt[i] += precnt[i - 1];
    }
    for(int i = 1; i <= k; i++) {
        int l, r, d;
        cin >> l >> r >> d;
        seg[i] = node{l, r, d};
        segl[l].push_back(i);
        segr[r].push_back(i);
    }
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
        for(auto id : segl[i]) {
            int l = seg[id].l, r = seg[id].r, d = seg[id].d;
            segs.insert({(r - l + 1 - d) / 2 + cnt - (precnt[r] - precnt[l - 1]), id});
            limit[id] = (r - l + 1 - d) / 2 + cnt - (precnt[r] - precnt[l - 1]); // 原先就有的-1要先减去
        }
        if(!arr[i]) {
            if(segs.empty() || segs.begin()->first >= cnt + 1) {
                cnt++;
                arr[i] = -1;
            } else {
                arr[i] = 1;
            }
        }
        for(auto id : segr[i]) {
            segs.erase({limit[id], id});
        }
    }
    if(!check(n, k)) cout << "Impossible" << endl;
    else {
        for(int i = 1; i <= n; i++) cout << arr[i] << " \n"[i == n];
    }
}
posted @ 2021-08-25 22:34  limil  阅读(44)  评论(0编辑  收藏  举报