区间选点(差分)

题意:

给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点
要求使用差分约束系统的解法解决这道题

输入要求:
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。

输出要求:
输出一个整数表示最少选取的点的个数

样例输入:

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

样例输出:

6

思路:

差分:
对于差分约束中的每一个不等式约束Xi-Xj<=Ck都可以移项变形为 Xi<=Xj+Ck
如果令Ck=𝑤(𝑖,𝑗),𝑑𝑖𝑠[i]=Xi,𝑑𝑖𝑠[𝑗]=Xj,那么原式变为𝑑𝑖𝑠[𝑖]≤ 𝑑𝑖𝑠[𝑗]+𝑤(𝑖,𝑗),与最短路问题中的松弛操作相似
差分约束问题最后实际上变成最短路问题
这里有两点需要注意:
1)可能出现负环问题,所以使用SPFA
2)最后求得的答案都是界值,跑最短路得到的是一组最大解(上界),如果想要求最小解(下界)则将≤变成≥然后跑最长路即可

具体实现:
1)先要构造Xi<=Xj+Ck的算是形式,这道题给的区间中点的个数, 记dis[𝑖]表示数轴上[0,𝑖]之间选点的个数 , 对于第i个区间[ai,bi]需要满足dis[bi]−dis[ai−1]≥ci
下面就可以构造图了,区间中点的个数即成图后边上的权值

2)求选点的最小值(下界),转化为≥不等式组跑最长路
注意点!!使用了上周的SPFA
这里有个约束条件:需要保证dis是有意义的 即有0≤dis[i]-dis[i-1]≤1

总结:

这道题用查分解的关键是能够得到符合差分使用条件的关系,即dis[bi]−dis[ai−1]≥ci,这一步可能比较难想

代码:

#include<cstdio>
#include<queue> 
using namespace std;
const int inf=5*1e8;

int a[50010],dis[50010],cnt[50010],vis[50010];				//记录最长路径,当前最长路的边数,点是否遍历过
int pre[50010],fvis[50010];						//记录是否属于负环 
queue<int> q; 
int n,tot;

struct edge{
	int v,nxt,w;
}e[200010]; 
int head[50010];

void add(int a,int b,int c){				//加边 
	e[++tot].v=b; e[tot].nxt=head[a]; e[tot].w=c;
	head[a]=tot;
}

void init(){
	for(int i=1;i<50010;i++){
		dis[i]=-inf; cnt[i]=0; head[i]=0;
		vis[i]=0;  fvis[i]=0;  pre[i]=0;
	}
	tot=0;
}

void dfs(int s){
	fvis[s]=1;										//标记属于负环 
	for(int i=head[s];i!=0;i=e[i].nxt){
		if(fvis[e[i].v]!=1)
			dfs(e[i].v);
	}
} 

void SPFA(int s){
	dis[s]=0; vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int u=q.front(); q.pop();
		vis[u]=0;						/
		if(fvis[u]) continue;						//已经属于负环 
		for(int i=head[u];i!=0;i=e[i].nxt){
			int v=e[i].v;
			if(dis[v]<dis[u]+e[i].w){			//需要松弛 //跑最大树 
				cnt[v]=cnt[u]+1;					//更新最短边边数
				if(cnt[v]>=50000) 					//负环
					dfs(v);							//找到能到的点 
				dis[v]=dis[u]+e[i].w;				//更新最短路长度
				pre[v]=u;
				if(!vis[v]){
					q.push(v);						//未到达过,扔到队列
					vis[v]=1; 
				} 
			}
		}
	}
} 

int main(){
	int a,b,c,b_max=0;
	scanf("%d",&n);
	init();
	for(int i=0;i<n;i++){
		scanf("%d%d%d",&a,&b,&c);
		add(a,b+1,c);								//整体右移,不使用a-1,使用b+1 
		if(b_max<b+1)  b_max=b+1;					//记录b的最大值 
	}
	for(int i=1;i<=b_max;i++){						//保证dis有意义 0≤dis[i]-dis[i-1]≤1  
		add(i-1,i,0);
		add(i,i-1,-1);
	}
	SPFA(0);
	printf("%d\n",dis[b_max]);
	return 0; 
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值