题目链接
𝑅𝑒𝑘𝑖在课余会接受一些民间的鹰眼类委托,即远距离的狙击监视防卫。
𝑅𝑒𝑘𝑖一共接到了𝑚份委托,这些委托与𝑛个直线排布的监视点相关。
第𝑖份委托的内容为:对于区间[𝑙𝑖, 𝑟𝑖]中的监视点,至少要防卫其中的𝑘𝑖个。
𝑅𝑒𝑘𝑖必须完成全部委托,并且希望选取尽量少的监视点来防卫。
输入描述:
第一行,两个正整数𝑛,𝑚。
接下来𝑚行,每行三个整数𝑙𝑖,𝑟𝑖,𝑘𝑖。
输出描述:
一行,一个整数,即所需防卫的最少监视点数量。
题解:贪心和线段树,我们把所有委托根据其右边界的大小从小到大排序排序,然后遍历这些委托,但遍历到某个委托时查询该委托区间已有的监视点,看看还缺多少,然后将缺的全部尽可能的补在靠右的位置,为什么了,因为我们根据其右边界大小从小到大枚举这些委托,你现在所处理的委托的右边界一定大于等于前一个的右边界,而前一个委托的要求已经达到,你将监视点补在前段几乎没有作用,而补在后端,因为下一个委托的右边界一定大于等于该委托的右边界,所以补在右边才能使这些监视点的作用尽可能的被下一个委托所利用
代码如下:
线段树做法
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
int s[maxn * 4];
int f[maxn * 4];
int n, m;
vector<int> g[maxn];
struct X {
int L, R, k;
}op[maxn];
void pushDown(int rt) {
if(f[rt] == 0) return;
s[2 * rt] = 0;
f[2 * rt] = 1;
s[2 * rt + 1] = 0;
f[2 * rt + 1] = 1;
f[rt] = 0;
}
void pushUp(int rt) {
s[rt] = s[2 * rt] + s[2 * rt + 1];
}
// 区间 [L, R] 中 0 的个数
int sum(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) {
return s[rt];
}
int mid = (l + r) / 2;
int left = 0;
int right = 0;
pushDown(rt);
if(L <= mid) left = sum(L, R, l, mid, 2 * rt);
if(R > mid) right = sum(L, R, mid + 1, r, 2 * rt + 1);
pushUp(rt);
return left + right;
}
// 区间 [L, R] 覆盖为 1
void update(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) {
s[rt] = 0;
f[rt] = 1;
return;
}
int mid = (l + r) / 2;
pushDown(rt);
if(L <= mid) update(L, R, l, mid, 2 * rt);
if(R > mid) update(L, R, mid + 1, r, 2 * rt + 1);
pushUp(rt);
}
void build(int l, int r, int rt) {
s[rt] = r - l + 1;
if(l == r) return;
int mid = (l + r) / 2;
build(l, mid, 2 * rt);
build(mid + 1, r, 2 * rt + 1);
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++) {
scanf("%d%d%d", &op[i].L, &op[i].R, &op[i].k);
g[op[i].R].push_back(i);
}
int ans = 0;
build(1, n, 1);
for(int i = 0; i <= 500000; i ++) {
int sz = g[i].size();
for(int j = 0; j < sz; j ++) {
int id = g[i][j];
int len = op[id].R - op[id].L + 1;
int Sum = sum(op[id].L, op[id].R, 1, n, 1);
if(len - Sum >= op[id].k) {
continue;
}
int pos = -1;
int left = op[id].L, right = op[id].R;
while(left <= right) {
int mid = (left + right) / 2;
if(sum(mid, op[id].R, 1, n, 1) >= op[id].k - (len - Sum)) {
pos = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
update(pos, op[id].R, 1, n, 1);
ans = ans + op[id].k - (len - Sum);
}
}
printf("%d\n", ans);
return 0;
}
树状数组做法:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 500000 + 10;
const int INF = 1e9 + 7;
int tree[maxn], n, m;
struct node
{
int l, r, w;
node(){}
bool operator < (const node& a)const
{
return r < a.r;
}
}a[maxn << 1];
int lowbit(int x)
{
return (x & -x);
}
int sum(int x)
{
int ans = 0;
while(x)
{
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
bool vis[maxn];
void add(int x, int d)
{
while(x <= n)
{
tree[x] += d;
x += lowbit(x);
}
}
inline int read() {
char ch = getchar(); int x = 0, f = 1;
while(ch < '0' || ch > '9') {
if(ch == '-') f = -1;
ch = getchar();
} while('0' <= ch && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
} return x * f;
}
int main()
{
n = read(), m = read();
for(int i = 1; i <= m; i++)
a[i].l = read(), a[i].r = read(), a[i].w = read();
sort(a + 1, a + 1 + m);
for(int i = 1; i <= m; i++)
{
int tmp = sum(a[i].r) - sum(a[i].l - 1);
for(int j = a[i].r; j >= a[i].l && tmp < a[i].w; j--)
if(!vis[j])vis[j] = 1, add(j, 1), tmp++;
}
printf("%d\n", sum(n));
return 0;
}