贪心策略_区间选点问题
基本做法
Description
You are given n closed, integer intervals [ai, bi] and n integers c1, …, cn.
Write a program that:
reads the number of intervals, their end points and integers c1, …, cn from the standard input,
computes the minimal size of a set Z of integers which has at least ci common elements with interval [ai, bi], for each i=1,2,…,n,
writes the answer to the standard output.
Input
The first line of the input contains an integer n (1 <= n <= 50000) – the number of intervals. The following n lines describe the intervals. The (i+1)-th line of the input contains three integers ai, bi and ci separated by single spaces and such that 0 <= ai <= bi <= 50000 and 1 <= ci <= bi - ai+1.
Output
The output contains exactly one integer equal to the minimal size of set Z sharing at least ci elements with interval [ai, bi], for each i=1,2,…,n.
Sample Input
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
Sample Output
6
**思路:**将每次的按照终点位置进行从小到大排序,当前区间如果打点数目不满足,则按照从后到前的顺序依次打点,如果满足,则进行下一段操作。需要使用到一个数组记录打点的位置,如果该点打点,则赋值为1,否则为0
代码
package 贪心算法;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static Scanner scanner = new Scanner(System.in);
static int n;
private static class Interval implements Comparable<Interval> {
int s, t, n;
public Interval(int s, int t, int n) {
this.s = s;
this.t = t;
this.n = n;
}
@Override
public int compareTo(Interval o) {
int x = this.t - o.t;
if (x == 0) {
return this.s - o.s;
} else {
return x;
}
}
}
//记录数组中的打点数目
static int sum(int[] arr,int s,int t) {
int sum=0;
while(s<=t) {
if(arr[s]==1) {
sum++;
}
s++;
}
return sum;
}
public static void main(String[] args) {
n=scanner.nextInt();
Interval[] arr=new Interval[n];
for(int i=0;i<n;i++) {
arr[i]=new Interval(scanner.nextInt(), scanner.nextInt(), scanner.nextInt());
}
Arrays.sort(arr);
int max=arr[n-1].t;
int[] vis=new int[max+1]; //设置标记数组
Arrays.fill(vis, 0); // 初始化都为0
// 贪心策略
for(int i=0;i<n;i++) {
int s=arr[i].s;
int t=arr[i].t;
int n=arr[i].n;
int need=n-sum(vis, s, t); //当前需要打点的数目等于:原来需要打点数目减去已经打点数目
while(need>0) { //还有需要打点的,从末尾往前打点
if(vis[t]==0) { //当前点可以打
vis[t]=1;
t--;
need--;
}else {
t--;
}
}
}
System.out.println(sum(vis, 0, max));
}
}
此段代码会有超时问题,因为在记录一段区间内的打点数目时,当区间过大会超时
利用树状数组改进
思路跟上面的一样,不过在求解一段区间内的打点数目方法改变了
import java.util.Arrays;
import java.util.Scanner;
public class 策略_区间选点问题_树状数组 {
static Scanner scanner = new Scanner(System.in);
static int n;
static int ans;
static int max=50001;
static int[] pre=new int[max]; //树状数组记录每个的前缀和
private static class Interval implements Comparable<Interval> {
int s, t, n;
public Interval(int s, int t, int n) {
this.s = s;
this.t = t;
this.n = n;
}
@Override
public int compareTo(Interval o) {
int x = this.t - o.t;
if (x == 0) {
return this.s - o.s;
} else {
return x;
}
}
}
static int lowbit(int x) { //这个函数的功能就是求某一个数的二进制表示中最低的一位1所表示的数,比如6(110)返回2(10)
return x&(-x);
}
static void add(int i,int value) { //构造树状数组的前缀和
while(i<=50001) {
pre[i]+=value; //当前节点加value
i+=lowbit(i); //子节加value,在他之上的父节点也要加value;
}
}
static int get_sum(int i) { //求前缀和
int sum=0;
while(i>0) {
sum+=pre[i];
i-=lowbit(i);
}
return sum;
}
public static void main(String[] args) {
n=scanner.nextInt();
Interval[] arr=new Interval[n];
for(int i=0;i<n;i++) {
arr[i]=new Interval(scanner.nextInt(), scanner.nextInt(), scanner.nextInt());
}
Arrays.sort(arr);
int max=arr[n-1].t;
int[] vis=new int[max+1]; //设置标记数组
Arrays.fill(vis, 0); // 初始化都为0
// 贪心策略
for(int i=0;i<n;i++) {
int s=arr[i].s;
int t=arr[i].t;
int n=arr[i].n;
int need=n-(get_sum(t)-get_sum(s-1));
while(need>0) { //还有需要打点的,从末尾往前打点
if(vis[t]==0) { //当前点可以打
vis[t]=1;
ans++;
add(t, 1);
t--;
need--;
}else {
t--;
}
}
}
System.out.println(ans);
}
}
学习参考
https://www.bilibili.com/video/BV1e7411T7FV?t=1232&p=127 思路解答
https://blog.csdn.net/qq_45724216/article/details/115097861 树状数组学习