POJ 1716 Integer Intervals 差分约束

题意是给你n个区间,n<=10^5,
每个区间有左右两个端点,a,b,a,b<=10^5
然后要你选一个点集,使这个点集在每个区间中至少有2两个点,求这个点集的点的个数的最小值
做法是贪心或者差分约束系统

贪心:就是说把所有区间按右端点升序排序,然后对于第一个区间,取最右边的那两个数,点数+2,因为越靠右越有可能被后面用到,从而使点数尽可能少
然后记录上次取的那两个数a1,a2,然后对于下一个区间,首先看看a1在不在这个区间中,在的话,这个区间肯定至少已经有两个数被取了,检查下个区间
如果a1不在,然后看看a2在不在,假如a2在的话,那就让a1=a2,a2为这个区间的最右端的值,点数+1
如果a2也不在,那a1和a2就是当前这个区间的最右端的两个数,点数+2
遍历一遍就可以

#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 100005
struct Node
{
	int l, r;
	Node() :l(0), r(0){}
	Node(int l, int r)
	{
		this->l = l; this->r = r;
	}
	bool operator <(Node &rhs)const
	{
		return r < rhs.r;
	}
};
Node interval[maxn];
int n;
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);
	scanf("%d", &n);
	int a, b;
	int ans = 0;
	for (int i = 0; i < n; ++i)
	{
		scanf("%d%d", &interval[i].l, &interval[i].r);
	}
	sort(interval, interval + n);
	ans = 2; a = interval[0].r - 1; b = interval[0].r;
	for (int i = 1; i < n; ++i)
	{
		if (b < interval[i].l)
		{
			a = interval[i].r - 1; b = interval[i].r;
			ans += 2;
		}
		else if (a < interval[i].l)
		{
			a = b; b = interval[i].r;
			++ans;
		}
	}
	printf("%d\n", ans);
	//while (1);
	return 0;
}

差分约束:我是第一次写差分约束,就谈谈我浅显的理解吧
我们用sum[i]表示[0,i)中所取的点的个数,所以对于每个区间[a,b],有sum[b+1]>=sum[a]+2
但是这个约束条件还不够,这个约束条件只约束了一部分点,不连续
对于所有的点,sum[i+1]>=sum[i],sum[i+1]-sum[i]<=1,一个是表示后一个的肯定要比当前这个要大或者相等,另一个表示后一个比当前这个最多大1
然后这样,图就可以建了,
差分约束有两种,一种是>=的不等式,求的是最长路,另一种是<=的不等式,求的是最短路

最长路的:
dist[i]表示[0,i)中包含的数的个数
dist[b+1]>=dist[a]+2
dist[i+1]>=dist[i]
dist[i]>=dist[i+1]-1
然后这个肯定是求最长路的,但是是从哪到哪的最长路呢?
首先我们就说我们涉及到的所有区间端点中 ,假设最小的为minv,最大的为maxv,
然后肯定起点从 minv就可以,[0,minv)肯定为0,终点为maxv即可,因为我们已经求出所有我们想求的点了
然后,这个差分约束的不等式dist[b+1]>=dist[a]+2,是从a到b+1的边,我们求的是到b+1的最长路,所以依次类推,我们从minv出发,求到maxv的最长路即可,dist[minv]初始为0即可
还有一个问题就是初始化的问题,一开始我也是把所有点初始化为0的,然后就WA了,初始化为-1之后才AC,
这个原因,对拍后自己手写了手写发现,是因为要让所有的约束都运行一遍,初始化为-1,所有的点都会跑一遍更新,所以所有的约束都会跑到,跑到最后就是最优解
如果初始化为0的话,可能有些不是从minv开始的约束,而是从中间某个点开始的约束,约束到了最终答案,然后这个约束就没有跑到,
所以用一个超级源点把所有点都连条边权为0的边也可以,然后从那个超级源点跑spfa

#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 10005
#define maxm 300055
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;
	scanf("%d", &m);
	n = 0;
	int minv = maxn, maxv = 0;
	for (int i = 0; i < m; ++i)
	{
		scanf("%d%d", &a, &b);
		//dist[i]表示[0,i)中包含的数的个数
		//dist[b+1]>=dist[a]+2
		//dist[i+1]>=dist[i]
		//dist[i]>=dist[i+1]-1
		//>=就是求最长路,<=就是求最短路
		add(a, b + 1, 2);
		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 + 1, i, -1);
		add(i, i + 1, 0);
	}
	SPFA(minv);
	/*for (int i = minv; i <= maxv; ++i)
		printf("%d ", dist[i]);
	printf("\n");*/
	printf("%d\n", dist[maxv]);
	//while (1);
	return 0;
}

最短路:
dist[i]表示[0,i)中包含的数的个数
dist[a]<=dist[b+1]-2
dist[i]<=dist[i+1]
dist[i+1]<=dist[i]+1
>=就是求最长路,<=就是求最短路
然后所以这个约束就是求最短路,然后也与上面的类似,可以发现是从b+1到a的约束,所以要从maxv跑到minv,
但是还有一个问题就是,dist[maxv]初始化为0,表示起点为0,但是往后面更新呢,发现dist[i]是负数,这是什么情况呢?
后来想了想,发现,是这个边约束的是,前面的sum[i]比后面的少多少个点,其实我们最后得到的也是dist[maxv]-dist[minv],因为差分约束中,只跟边权有关系,而对于初始值,对于我们这种求解的个数的来说,没太大关系,所以这个的答案就是-dist[minv],然后也就可以了

#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 10005
#define maxm 100005
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 cntq[maxn];
int dist[maxn];
bool SPFA(int s)
{
	memset(inq, false, sizeof(inq));
	memset(cntq, 0, sizeof(int)*maxn);
	memset(dist, 0x3f, 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] < INF&&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;
	scanf("%d", &m);
	n = 0;
	int minv = maxn, maxv = 0;
	for (int i = 0; i < m; ++i)
	{
		scanf("%d%d", &a, &b);
		//dist[i]表示[0,i)中包含的数的个数
		//dist[a]<=dist[b+1]-2
		//dist[i]<=dist[i+1]
		//dist[i+1]<=dist[i]+1
		//>=就是求最长路,<=就是求最短路
		add(b + 1, a, -2);
		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, 1);
		add(i + 1, i, 0);
	}
	SPFA(maxv);
	/*for (int i = minv; i <= maxv; ++i)
		printf("%d ", dist[i]);
	printf("\n");*/
	printf("%d\n", -dist[minv]);
	//while (1);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/code12hour/article/details/52073871