清理牛棚(dp+线段树优化)

【问题描述】
 
  FJ的奶牛们从小娇生惯养,它们无法容忍牛棚里的任何脏东西。FJ发现,如果要使这群有洁癖的奶牛满意,他不得不雇用她们中的一些来清扫牛棚。


  FJ的奶牛中有N头牛愿意通过清扫牛棚来挣一些零花钱。由于在某个时段中奶牛们会在牛棚里随时随地地乱扔垃圾,自然地要求在这段时间里,无论什么时候至少要有一头奶牛正在打扫。需要打扫得时段从某一天的第M秒开始,到第E秒结束。注意这里的秒时指时段而不是时间点,也就是说,每天需要打扫得总时间是E-M+1秒。 


  FJ已经从每头牛那里得到它们愿意接受的工作计划:对于某一头牛,她每天都愿意在T1..T2秒的时段内工作(M<=T1<=T2<=E),所要求的报酬是S美元。与需打扫得时段描述一样,如果一头牛愿意工作的时段是每天10..20秒,那总共工作的时间是11秒,而不是10秒。FJ一旦决定雇用某一头牛,就必须付给他全额的工资,而不能只让她工作一段时间,然后再决定这段时间她愿意工作的总时间中所占百分比来决定它的工资。 


  现在请你帮助FJ决定该雇用那些奶牛以保持牛棚的清洁,当然,在能让奶牛们满意的前提下,FJ希望总花费尽量小。
 
【输入格式】
 
  第1行:3个正整数:N,M,E,用空格隔开。
  第2到第N+1行:第i+1行给出编号为i的奶牛的工作计划,即3个用空格隔开的正整数T1,T2,S(M<=T1<=T2<=E)。
 
【输出格式】
 
  输出一个整数,表示FJ需要为牛棚清洁工作支付的最少费用。如果清理工作不可能完成,那么输出-1。
 
【输入样例】
 
3 0 4
0 2 3
3 4 2
0 0 1
 
【输出样例】
 
5
 
【样例解释】
 
  有3头奶牛,牛棚在第0到第4秒需要打扫。第1头牛想要在第0,1,2秒内工作,为此它要求的报酬是3美元,其余依次类推。 


  FJ雇用前两头牛清扫牛棚,可以只花5美元就完成一整天的清扫。
 
【数据范围】
 
1<=N<=10000 0<=M<=E<=86399 0<=S<=500000

其中60%的数据中N<=500


方法来自:https://blog.csdn.net/w_yqts/article/details/75444800


先把每头奶牛按T1排序;设f[i]表示覆盖M-i需要的最小花费,每当枚举到奶牛K,工作时间为T1~T2,工资为S,状态转移方程为:f[k]=min(f[j] | M<=j<=T1-1)+S;且T1<=k<=T2;

理由:第K头牛可以覆盖T1~T2,因为是按照左端点排序,如果发现T1-1没有被覆盖过,说明中间断开,无解。其次,T1~T2表示一个区间,如果这头牛选了,那么T1~T2都可以被覆盖,相当于把f[T1]~f[T2]都更新一下。分析发现,每次需要用到f[T1-1],属于点查找,更新时要更新f[T1]~f[T2],属于区间修改,自然想到线段树优化。


#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=10005;
const int maxm=100005;
const LL INF=7000000000;
int M,E,n,rt,np=0;
struct data{int lc,rc;LL min_f,set;}tree[maxm*2];//f[i]:表示从M覆盖到i需要的最小花费 
struct data2{int t1,t2,s;}cow[maxn];

bool cmp(data2 a,data2 b) {return a.t1<b.t1;}//按左端点排序 

char c;
inline void scan(int &x)
{
	for(c=getchar();c<'0'||c>'9';c=getchar());
	for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
}

inline void pushup(int now)
{
	int l=tree[now].lc,r=tree[now].rc;
	tree[now].min_f=min(tree[l].min_f,tree[r].min_f);
	tree[now].set=-1;
}

inline void build(int &now,int L,int R)
{
	now=++np;
	tree[now].min_f=INF;
	tree[now].set=INF;
	if(L==R) return;
	
	
	int m=(L+R)/2;
	build(tree[now].lc,L,m);
	build(tree[now].rc,m+1,R);
}

inline void pushdown(int now)
{
	if(tree[now].set==INF) return;
	int l=tree[now].lc,r=tree[now].rc;
	
	tree[l].set=min(tree[l].set,tree[now].set);
	tree[r].set=min(tree[r].set,tree[now].set);
	tree[l].min_f=min(tree[l].min_f,tree[now].set);
	tree[r].min_f=min(tree[r].min_f,tree[now].set);
	
	tree[now].set=INF;
}

inline LL query(int now,int L,int R,int i)
{
	if(i<L) return 0;//如果是在M之前,花费为0 
	pushdown(now);
	if(L==R) return tree[now].min_f;//查找到f[i],即从覆盖M到i需要的最少花费 
	
	int m=(L+R)/2;
	if(i<=m) return query(tree[now].lc,L,m,i);
	else return query(tree[now].rc,m+1,R,i);
}

inline void update(int now,int L,int R,int i,int j,LL d)
{
	pushdown(now);
	if(L>=i&&R<=j)
	{
		tree[now].min_f=min(tree[now].min_f,d);
		tree[now].set=d;
		return;
	}
	
	int m=(L+R)/2;
	if(i<=m) update(tree[now].lc,L,m,i,j,d);
	if(j>m) update(tree[now].rc,m+1,R,i,j,d);
}

int main()
{
//	freopen("in.txt","r",stdin);
	scan(n);scan(M);scan(E);
	build(rt,M,E);
	for(int i=1;i<=n;i++)
	{
		scan(cow[i].t1);
		scan(cow[i].t2);
		scan(cow[i].s);
	}
	sort(cow+1,cow+1+n,cmp);
	
	for(int i=1;i<=n;i++)
	{
		int L=cow[i].t1,R=cow[i].t2,S=cow[i].s;
		LL min_now=query(rt,M,E,L-1);//找到M到L-1最小花费,因为这只牛覆盖的是L-R 
		if(min_now==INF)//如果L-1没有任何覆盖过,说明中间断开了,无解 
		{
			printf("%d",-1);
			return 0;
		}
		update(rt,M,E,L,R,min_now+S);//更新,M到L-R这一段都可以覆盖 
	}
	LL ans=query(rt,M,E,E);//查找从M到E的最小花费,因为之前更新时肯定把E这个点囊括在区间里的 
	printf("%lld",ans<INF?ans:-1);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/80789889