[JOISC2020] 治療計画

前言

也许是人生第一道线段树优化建图。

也许不是。

题目

洛谷

LibreOJ

讲解

警告!无图!

想象一条线段表示国民,我们需要用治疗方案(线段)覆盖这些国民,但是这些国民都会每秒被相邻的感染者感染,为了不使之前的国民再次被感染,我们两次治疗方案一定要能够衔接起来。

接下来就是开脑洞的时候了,我们抛开时间这一维不看,单纯只用治疗方案覆盖这些国民,我们发现一定是从 \(1\) 号国民覆盖到 \(n\) 号国民。

而两次治疗方法 \(i,j\) ( \(i\) 先于 \(j\) 实施) 能够衔接的条件为:

\[r_i-l_j\ge |t_i-t_j| \]

其中 \(l,r\) 表示治疗方案的左右断电,\(t\) 表示实施治疗方法时间。

如果我们把绝对值拆开,那么就有两种情况:

  • \(t_i > t_j,r_i-t_i\ge l_j-t_j.\)
  • \(t_i<t_j,r_i+t_i\ge l_j+t_j.\)

取等的情况时候随便放到一边就行。

于是我们可以把治疗方案看做节点,如果一对有序治疗方案满足上述条件,那么连边,此时我们可以将边权放在终点,也就是把边权转换为点权,然后跑最短路。

由于点权的特殊性,我们只需要目标位置 \(j\) 的前驱 \(i\) 权值最小即可贪心松弛,跑 \(\tt dijkstra\) 就行。

而且每条边一定只会松弛一次,这为复杂度提供保障。

什么?你问我怎么找满足条件的边?可以发现上面两种情况的不等式 \(i,j\) 独立,分情况建两棵线段树,然后暴力找就行了。因为松弛一次,所以每次找到了之后直接把权值赋为 \(+\infty\),表示以后不会再来就行。

时间复杂度 \(O(m\log_2m)\)

代码

//12252024832524
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXM = 100005;
const int INF = 0x3f3f3f3f << 1;
int n,m,POS;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

LL dis[MAXM];
struct Cure
{
	int t,l,r,val;
	void Get(){t = Read(); l = Read(); r = Read(); val = Read();}
	bool operator < (const Cure &px)const{
		return t < px.t;
	}
}c[MAXM];

struct node
{
	int x; LL w;
	node(){}
	node(int x1,LL w1){
		x = x1;
		w = w1;
	}
	bool operator < (const node &px)const{
		return w > px.w;
	}
};
priority_queue<node> q;

#define lc (x<<1)
#define rc (x<<1|1)
struct SegmentTree
{
	int MIN[MAXM << 2];
	
	void up(int x){MIN[x] = Min(MIN[lc],MIN[rc]);}
	
	void Build(int x,int l,int r,int v)
	{
		if(l == r) {MIN[x] = c[l].l + v*c[l].t;return;}
		int mid = l+r >> 1;
		Build(lc,l,mid,v); Build(rc,mid+1,r,v);
		up(x);
	}
}st[2];
void Del(int x,int l,int r,int pos)
{
	if(l == r)
	{
		st[0].MIN[x] = st[1].MIN[x] = INF;
		return;
	}
	int mid = l+r >> 1;
	if(pos <= mid) Del(lc,l,mid,pos);
	else Del(rc,mid+1,r,pos);
	st[0].up(x); st[1].up(x);
}
void Solve(int opt,int x,int l,int r,int ql,int qr,int v)
{
	if(st[opt].MIN[x] > v) return;
	if(l == r)
	{
		q.push(node(l,dis[l] = dis[POS] + c[l].val));
		st[0].MIN[x] = st[1].MIN[x] = INF;
		return;
	}
	int mid = (l+r) >> 1;
	if(ql <= mid) Solve(opt,lc,l,mid,ql,qr,v);
	if(mid+1 <= qr) Solve(opt,rc,mid+1,r,ql,qr,v);
	st[0].up(x); st[1].up(x);
} 

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); m = Read();
	for(int i = 1;i <= m;++ i) c[i].Get();
	sort(c+1,c+m+1); memset(dis,0x3f,sizeof(dis));
	st[0].Build(1,1,m,-1); st[1].Build(1,1,m,1);
	for(int i = 1;i <= m;++ i)
		if(c[i].l == 1)
		{
			q.push(node(i,dis[i] = c[i].val));
			Del(1,1,m,i);
		}
	while(!q.empty())
	{
		node t = q.top(); q.pop();
		POS = t.x;
		if(c[POS].r == n){Put(dis[POS]);return 0;}
		if(POS ^ 1) Solve(0,1,1,m,1,POS-1,c[POS].r - c[POS].t + 1);
		if(POS ^ m) Solve(1,1,1,m,POS+1,m,c[POS].r + c[POS].t + 1);
	} 
	Put(-1);
	return 0;
}
/*
c[i].r - c[j].l >= Abs(c[i].t-c[j].t)
c[i].t > c[j].t
	c[i].r - c[i].t >= c[j].l - c[j].t
c[i].t < c[j].t
	c[i].r + c[i].t >= c[j].l + c[j].t
*/
posted @ 2021-07-17 10:14  皮皮刘  阅读(62)  评论(0编辑  收藏  举报