题目
给定一个整数序列({a_i})满足(-1le a_ile 1),给定(k)个约束条件((l,r,d))代表(sumlimits_{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 '
'
#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] << "
"[i == n];
}
}
方法2
按照常规的思路想就是,一开始将0填充为1,然后从左往右遍历可修改位置,判断此时修改成-1是否还满足所有约束条件,如果满足就贪心地改成-1;否则遍历下一个可修改位置。这样的正确性是显然的,但暴力会超时。
考虑优化。对于约束条件((l,r,d)),其等价形式就是区间((l,r))内最多有(lfloorfrac{r-l+1-d}{2} floor)个-1。
因此可以维护一个数据结构,当遍历到位置(i),将左端点的为(i)的区间以其可用的-1数量插入数据结构,然后查询当前位置(i)如果变为-1,数据结构中最少的可用的-1数量是否大于0。如果发生修改,就对将数据结构中所有的可用的-1数量均减去1。最后再将右端点为(i)的区间全部弹出。然后处理下一个位置。
显然,用这种方法,任意时刻到达位置(i),数据结构中就含有所有包含位置(i)的区间。如果要支持修改,会很难实现,所以可以反过来,所有减去1,相当于其他加上1。维护一个(cnt)代表当前修改了多少-1,然后插入(lfloorfrac{r-l+1-d}{2} floor+cnt),查询时和当前(cnt)比较即可。这样就只有插入查询和删除,可以用set实现。思想类似有一题聊天室的题,用差分代替每次修改。
#include <bits/stdc++.h>
#define endl '
'
#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] << "
"[i == n];
}
}