是上一个 POJ 1716的加强版,上一个是每个区间至少有两个,这个是对于第i个区间,至少有ci个
给你n个区间,n<=50000,
每个区间有左右两个端点,a,b,a,b<=50000
然后要你选一个点集,使这个点集在第i个区间中,至少有ci个个点,求这个点集的点的个数的最小值
做法还是贪心或者差分约束系
但是贪心要优化一下
先说差分约束吧
dist[i]表示[0,i)中包含的数的个数
dist[b+1]>=dist[a]+c
dist[i+1]>=dist[i]
dist[i]>=dist[i+1]-1
>=就是求最长路,<=就是求最短路
dist[b+1]>=dist[a]+c
dist[i+1]>=dist[i]
dist[i]>=dist[i+1]-1
>=就是求最长路,<=就是求最短路
跟上一道题一样的套路,把2改成c就可以了,具体解释建上一道题
#include <string>
#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <fstream>
#include <queue>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define maxn 50005
#define maxm 200005
struct Edge
{
int next, to, w;
Edge() :next(0), to(0), w(0) { }
}edge[maxm];
int head[maxn], cnt = 0;
void add(int u, int v, int w)
{
edge[cnt].w = w;
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
int n, m;
bool inq[maxn];
int dist[maxn];
bool SPFA(int s)
{
memset(inq, false, sizeof(inq));
memset(dist, -1, sizeof(int)*maxn);
queue<int> Q;
dist[s] = 0;
inq[s] = true;
Q.push(s);
while (!Q.empty())
{
int u = Q.front(); Q.pop();
int v;
inq[u] = false;
for (int i = head[u]; i != -1; i = edge[i].next)
{
//printf("u %d %d v %d %d edge %d\n", u, dist[u], edge[i].to, dist[edge[i].to], edge[i].w);
if (dist[u] + edge[i].w > dist[edge[i].to])
{
v = edge[i].to;
dist[v] = dist[u] + edge[i].w;
if (!inq[v])
{
Q.push(v);
inq[v] = true;
}
}
}
}
return true;
}
int main()
{
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
//ios::sync_with_stdio(false);
//cin.tie(0); cout.tie(0);
//ifstream in;
//in.open("input.txt", ios::in);
cnt = 0; memset(head, -1, sizeof(int)*maxn);
int a, b, c;
scanf("%d", &m);
n = 0;
int minv = maxn, maxv = 0;
for (int i = 0; i < m; ++i)
{
scanf("%d%d%d", &a, &b, &c);
//dist[i]表示[0,i)中包含的数的个数
//dist[b+1]>=dist[a]+c
//dist[i+1]>=dist[i]
//dist[i]>=dist[i+1]-1
//>=就是求最长路,<=就是求最短路
add(a, b + 1, c);
minv = min(a, minv); maxv = max(b + 1, maxv);
}
//printf("minv %d maxv %d\n", minv, maxv);
for (int i = minv; i < maxv; ++i)
{
add(i, i + 1, 0);
add(i + 1, i, -1);
}
SPFA(minv);
/*for (int i = minv; i <= maxv; ++i)
printf("%d ", dist[i]);
printf("\n");*/
printf("%d\n", dist[maxv]);
//while (1);
return 0;
}
另一个做法:贪心+优化
上一个题我们说可以贪心搞,把所有区间按右端点排序,然后每次取时取尽量靠右的,然后记录上次取的是哪两个,然后来决定这个区间取几个
现在这个数字不固定了,我们还可以是一样的思路,就是先排序,排序后,第一个区间肯定是取最右边的c0个,然后对于下一个区间,然后我们记录上一次取了哪些,就是说我们去查我们这个区间现在已经取了多少了,我们还要再取多少个,如果还有再取的话,就还是尽量取最右端的那样的方式
但是怎么维护呢?我们不可能对于每个区间,都去扫一遍,n^2的做法肯定会T,那如果我们记录之前出现的那些的位置呢?
因为ci不确定,所以我们要记录之前所有我们取的点都是哪些,然后每次查询这个区间取了多少个了,然后如果需要再取,就取并记录
显然这个用数组的话,查询是O(n),记录是O(1),所以不可行
但是显然是区间更新和单点查询,所以我们可以用树状数组去维护,然后就是log级别的,然后就可以了
另:在网上还看到了一种做法是二分+线段树,把这个理解为一个涂色的过程,然后开始也是相同的操作,但是对于后面操作每个区间时,二分去查询我应该从哪个点开始把后面的那一段全都涂色,能使得涂色总和为ci,因为已涂色的部分是单调递增的,所以可以二分查询,但是线段树更新就是类似区间染色的方式去更新的,好麻烦哦,就懒得这样写了,可以自己搜一下或者意会意会
#include <string>
#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <fstream>
#include <queue>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define maxn 50005
struct Node
{
int l, r, c;
Node() :l(0), r(0), c(0){}
Node(int l, int r, int c)
{
this->l = l; this->r = r; this->c = c;
}
bool operator <(Node &rhs)const
{
return r < rhs.r;
}
};
Node line[maxn];
bool used[maxn];
int tree[maxn];
int n = 0;
int query(int i)
{
int sum = 0;
while (i)
{
sum += tree[i];
i -= i&(-i);
}
return sum;
}
void update(int i, int val)
{
while (i <= n)
{
tree[i] += val;
i += i&(-i);
}
}
int main()
{
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
//ios::sync_with_stdio(false);
//cin.tie(0); cout.tie(0);
//ifstream in;
//in.open("input.txt", ios::in);
int m;
scanf("%d", &m);
for (int i = 0; i < m; ++i)
{
scanf("%d%d%d", &line[i].l, &line[i].r, &line[i].c);
++line[i].l; ++line[i].r;
}
sort(line, line + m);
n = line[m - 1].r;
int ans = 0;
for (int i = 0; i < line[0].c; ++i)
{
update(line[0].r - i, 1);
used[line[0].r - i] = true;
}
ans += line[0].c;
for (int i = 1; i < m; ++i)
{
int all = query(line[i].r) - query(line[i].l - 1);
if (all >= line[i].c)
continue;
int j = line[i].r;
while (j >= line[i].l)
{
if (!used[j])
{
update(j, 1);
used[j] = true;
++all; ++ans;
if (all >= line[i].c)
break;
}
--j;
}
}
printf("%d\n", ans);
//while (1);
return 0;
}