前言
也许是人生第一道线段树优化建图。
也许不是。
题目
讲解
警告!无图!
想象一条线段表示国民,我们需要用治疗方案(线段)覆盖这些国民,但是这些国民都会每秒被相邻的感染者感染,为了不使之前的国民再次被感染,我们两次治疗方案一定要能够衔接起来。
接下来就是开脑洞的时候了,我们抛开时间这一维不看,单纯只用治疗方案覆盖这些国民,我们发现一定是从 (1) 号国民覆盖到 (n) 号国民。
而两次治疗方法 (i,j) ( (i) 先于 (j) 实施) 能够衔接的条件为:
[r_i-l_jge |t_i-t_j|
]
其中 (l,r) 表示治疗方案的左右断电,(t) 表示实施治疗方法时间。
如果我们把绝对值拆开,那么就有两种情况:
- (t_i > t_j,r_i-t_ige l_j-t_j.)
- (t_i<t_j,r_i+t_ige l_j+t_j.)
取等的情况时候随便放到一边就行。
于是我们可以把治疗方案看做节点,如果一对有序治疗方案满足上述条件,那么连边,此时我们可以将边权放在终点,也就是把边权转换为点权,然后跑最短路。
由于点权的特殊性,我们只需要目标位置 (j) 的前驱 (i) 权值最小即可贪心松弛,跑 ( t dijkstra) 就行。
而且每条边一定只会松弛一次,这为复杂度提供保障。
什么?你问我怎么找满足条件的边?可以发现上面两种情况的不等式 (i,j) 独立,分情况建两棵线段树,然后暴力找就行了。因为松弛一次,所以每次找到了之后直接把权值赋为 (+infty),表示以后不会再来就行。
时间复杂度 (O(mlog_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
*/