LOJ3282 治疗计划
治疗计划
JOI 国有 \(N\) 个房屋,并从 \(1\) 到 \(N\) 编号。这些房屋沿一条直线升序排列。每个房屋有一个居民住在里面。住在编号为 \(x\) 的房屋里的居民用居民 \(x\) 表示。
最近,新冠病毒出现了,并且所有居民都被感染了。为了解决这个问题,有 \(M\) 个治疗方案被提出。第 \(i\ (1\le i\le M)\) 个治疗方案的花费为 \(C_i\)。如果执行计划 \(i\),则会发生以下事件:
- 在第 \(T_i\) 天的晚上,如果居民 \(x\) 满足 \(L_i\le x\le R_i\),且他感染了新冠病毒,那么他就会被治愈。
病毒按如下方式传染相邻的居民:
- 如果在某天的早晨,居民 \(x\ (1\le x\le N)\) 被病毒感染,那么在同一天的中午,居民 \(x-1\)(如果 \(x\ge 2\))和居民 \(x+1\)(如果 \(x\le N-1\))就会被感染。
一个已经被治愈的居民可以再次被病毒感染。
你是 JOI 国的首相,你需要选取某些方案,使得满足以下条件:
- 条件:在所有被选中的方案全部执行后,没有居民感染病毒。
在某一天可以执行多个计划。
写一个程序,给定房屋和治疗计划的信息,求出能否满足以上条件,若满足,求出最小可能花费。
对于所有数据,满足 \(1\le N\le 10^9,1\le M\le 10^5\)。
题解
假设有两个区间\([L_i,R_i],[L_j,R_j]\),其中区间\(i\)在区间\(j\)的左侧,那么两个区间能合并当且仅当\(R_i-L_j+1\geq |T_i-T_j|\)。
简单说一下对这个条件的理解:
-
\(T_i\leq T_j\),那么在\(j\)操作之前病毒从\(R_i+1\)侵蚀到了\(R_i+1-(T_j-T_i)\)这个位置。所以\(j\)操作左端点必须至少覆盖到\(R_i+1-(T_j-T_i)\)。
-
\(T_i>T_j\),那么在\(i\)操作之前病毒从\(L_j-1\)侵蚀到了\(L_j-1+(T_i-T_j)\)这个位置。所以\(i\)操作右端点必须至少覆盖到\(L_j-1+(T_i-T_j)\)。
两个条件合并起来就是\(R_i-L_j+1\geq |T_i-T_j|\)。
因为两个区间能否合并只与他们交集的长度和时间差的大小关系有关,所以我们并不需要关注谁先操作谁后操作。
因为没必要按照时间DP,所以不妨从左到右DP。
设\(F(i)\)表示\(T_i\)时刻,\([1,R_i]\)的病人都被治愈的最小花费。
-
初始化所有\(L_i=1\)的\(F(i)=C_i\)。
-
每次找出最小的\(F(i)\),尝试转移到其他的\(F(j)\)。
时间复杂度\(O(m^2)\)。
CO int N=2e5+10;
CO int64 inf=1e18;
int T[N],L[N],R[N],C[N];
bool vis[N];int64 F[N];
int main(){
int n=read<int>(),m=read<int>();
for(int i=1;i<=m;++i){
read(T[i]),read(L[i]),read(R[i]),read(C[i]);
F[i]=L[i]==1?C[i]:inf;
}
F[0]=inf;
function<int()> find=[&]()->int{
int ans=0;
for(int i=1;i<=m;++i)
if(!vis[i] and F[i]<F[ans]) ans=i;
return ans;
};
for(int x=find();x;x=find()){
vis[x]=1;
for(int i=1;i<=m;++i)
if(R[x]+1-abs(T[x]-T[i])>=L[i]) F[i]=min(F[i],F[x]+C[i]);
}
int64 ans=inf;
for(int i=1;i<=m;++i)
if(R[i]==n) ans=min(ans,F[i]);
printf("%lld\n",ans==inf?-1:ans);
return 0;
}
可以注意到,该动态规划的本质是Dijkstra,考虑优化这一个过程。
注意到连向\(F(i)\)的边的权值都是\(C_i\),所以在第一次能转移到它的时候直接认定它被完全松弛了。
对转移条件的绝对值进行分类讨论后可以简化为:
-
若\(T_i\geq T_j\),则需满足\(R_i−T_i+1\geq L_j−T_j\)。
-
若\(T_i< T_j\),则需满足\(R_i+T_i+1\geq L_j+T_j\)。
按\(T_i\)排序,然后用线段树来维护不等式条件。时间复杂度\(O(m\log m)\)。
CO int N=1e5+10;
CO int64 inf=1e18;
struct node {int t,l,r,c;} A[N];
priority_queue<pair<int64,int>,vector<pair<int64,int> >,greater<pair<int64,int> > > Q;
int64 dis[N];
namespace seg{
int minx[4*N],miny[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
IN void push_up(int x){
minx[x]=min(minx[lc],minx[rc]);
miny[x]=min(miny[lc],miny[rc]);
}
void modify(int x,int l,int r,int p,int vx,int vy){
if(l==r) {minx[x]=vx,miny[x]=vy; return;}
if(p<=mid) modify(lc,l,mid,p,vx,vy);
else modify(rc,mid+1,r,p,vx,vy);
push_up(x);
}
void queryx(int x,int l,int r,int ql,int qr,int vx,vector<int>&ans){
if(ql>qr or minx[x]>vx) return;
if(ql<=l and r<=qr){
if(l==r){
ans.push_back(l);
minx[x]=miny[x]=INT_MAX;
return;
}
queryx(lc,l,mid,ql,qr,vx,ans);
queryx(rc,mid+1,r,ql,qr,vx,ans);
return push_up(x);
}
if(ql<=mid) queryx(lc,l,mid,ql,qr,vx,ans);
if(qr>mid) queryx(rc,mid+1,r,ql,qr,vx,ans);
push_up(x);
}
void queryy(int x,int l,int r,int ql,int qr,int vy,vector<int>&ans){
if(ql>qr or miny[x]>vy) return;
if(ql<=l and r<=qr){
if(l==r){
ans.push_back(l);
minx[x]=miny[x]=INT_MAX;
return;
}
queryy(lc,l,mid,ql,qr,vy,ans);
queryy(rc,mid+1,r,ql,qr,vy,ans);
return push_up(x);
}
if(ql<=mid) queryy(lc,l,mid,ql,qr,vy,ans);
if(qr>mid) queryy(rc,mid+1,r,ql,qr,vy,ans);
push_up(x);
}
#undef lc
#undef rc
#undef mid
}
int main(){
int n=read<int>(),m=read<int>();
for(int i=1;i<=m;++i)
read(A[i].t),read(A[i].l),read(A[i].r),read(A[i].c);
sort(A+1,A+m+1,[&](CO node&a,CO node&b)->bool{
return a.t<b.t;
});
for(int i=1;i<=m;++i){
if(A[i].l==1){
dis[i]=A[i].c,Q.push({A[i].c,i});
seg::modify(1,1,m,i,INT_MAX,INT_MAX);
}
else{
dis[i]=inf;
seg::modify(1,1,m,i,A[i].l-A[i].t,A[i].l+A[i].t);
}
}
while(Q.size()){
int x=Q.top().second;Q.pop();
vector<int> trans;
seg::queryx(1,1,m,1,x-1,A[x].r-A[x].t+1,trans);
seg::queryy(1,1,m,x+1,m,A[x].r+A[x].t+1,trans);
for(int y:trans)
dis[y]=dis[x]+A[y].c,Q.push({dis[y],y});
}
int64 ans=inf;
for(int i=1;i<=m;++i)
if(A[i].r==n) ans=min(ans,dis[i]);
printf("%lld\n",ans==inf?-1:ans);
return 0;
}