POJ 1201 Intervals 差分约束

是上一个 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
>=就是求最长路,<=就是求最短路
跟上一道题一样的套路,把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;
}

猜你喜欢

转载自blog.csdn.net/code12hour/article/details/52074477
今日推荐