题解 P4644 【[Usaco2005 Dec]Cleaning Shifts 清理牛棚】

· · 题解

这里推荐一种数据结构优化DP的写法,

我们先把该题目转换成一个带权值的线段覆盖问题:

给定一个区间[l,r],接着再给n条带权值的边,边的左端点为a_i, 右端点为b_i,使用该边的代价为c_i

我们用f[x]表示覆盖[l,x]的最小代价

排序,按 b_i 的值升序排序,从 b_1b_n 遍历一遍, 我们能推出一个状态转移方程:f[b_i]=min ({f[x]},f[b_i])+c_i

# 证明 我们设上一段的左端点为p,右端点为q,如果区间能够被完全覆盖那么$a_{i-1}$一定小于等于q,而上一段的 f[q] 已经求出来了,根据无后效性,数组f在区间$[a_i-1,b_i]$的最小值就能由f[q]转移过来了;如果不能覆盖,该区间的最小值会是一个无穷大的值,输出-1,程序结束。 # 技巧 ```cpp 如果我们直接朴素(暴力)的来找数组f在区间a[i]-1到b[i]的最小值的话,肯定超时,于是我们可以用线段树来实现单点修改,区间查询的功能~ ``` # 放代码 ```cpp #include <cstdio> #include <iostream> #include <algorithm> #include <cstring> #include <string> #include <cmath> #include <cstdlib> using namespace std; const int maxn=251000; int n,l,r; int f[maxn]; struct node{ int a,b,c; }cow[maxn]; struct tree{ int l,r,dat; }t[maxn*4]; bool cmp(node a,node b){ return a.b<b.b; }//按b的值升序排序 void build(int p,int l,int r){ t[p].l=l,t[p].r=r; if(l==r){ t[p].dat=f[l]; return; } int mid=(l+r)/2; build(p*2,l,mid); build(p*2+1,mid+1,r); t[p].dat=min(t[p*2].dat,t[p*2+1].dat); }//建树 void change(int p,int x,int v){ if(t[p].l==t[p].r){ t[p].dat=v; return; } int mid=(t[p].l+t[p].r)/2; if(x<=mid) change(p*2,x,v); else change(p*2+1,x,v); t[p].dat=min(t[p*2].dat,t[p*2+1].dat); }//单点修改 int ask(int p,int l,int r){ if(l<=t[p].l&&r>=t[p].r){ return t[p].dat; } int mid=(t[p].l+t[p].r)/2; int val=1<<30; if(l<=mid) val=min(val,ask(p*2,l,r)); if(r>mid) val=min(val,ask(p*2+1,l,r)); return val; }//区间查询 int main(){ scanf("%d %d %d",&n,&l,&r); for(int i=1;i<=n;i++){ scanf("%d %d %d",&cow[i].a,&cow[i].b,&cow[i].c); } sort(cow+1,cow+n+1,cmp);//按b的值升序排序 memset(f,0x3f,sizeof(f)); f[l]=0;//从起点到起点,花费当然是0了 build(1,l,r); for(int i=1;i<=n;i++){ f[cow[i].b]=min(f[cow[i].b],ask(1,cow[i].a-1,cow[i].b)+cow[i].c); //找区间[ a[i]-1,b[i] ] 的最小值 change(1,cow[i].b,f[cow[i].b]); if(cow[i].b>=r){ if(f[cow[i].b]==0x3f3f3f3f){ printf("-1"); //不能完全覆盖 }else{ printf("%d",f[cow[i].b]); } //覆盖到了r直接输出最小值,程序结束 return 0; } } return 0; } ```