题目链接:http://acm.sgu.ru/problem.php?contest=0&problem=148
题意:在离著名的国家Berland不远的地方,有一个水下工作站。这个工作站有N层。已知:是第i层装有Wi的水,最多可以容纳Li的水,恐怖分子炸毁第i层的代价是Pi。第i层一旦被炸毁,该层所有的水都将倾泻到第i+1层。如果某一层的水量超过了它的容量(即Li),那么该层就将自动被毁坏,所有的水也会倾泻到下一层。Pivland的恐怖分子想要用最少的钱毁掉第N层,现在他雇佣你来计算,需要炸毁哪些层。
思路:令Si=W1+W2+…+Wi(特别的,S0=0)。不妨设恐怖分子炸毁的第高层是第p层(第一层是最高层,第N层是最底层)。因为恐怖分子的目标是毁灭第N层,所以水必须从第p层一直泻下去。如果存在一个i(i>p),满足Wp+Wp+1+…+Wi-1+Wi<=Li,也就是说前面几层的水全部泄下来也无法把第i层自动冲毁,那么就必须要使用炸药把它炸开了。所以恐怖分子需要炸毁的层的集合就是Bomb[p]={p}∪{i |i>p Si-Sp-1<=Li}。令Wi=Si-Li,那么:Bomb[p]={p}∪{i |i>p Wi<=Sp-1}。因此枚举p,然后看哪些层需要炸毁。这样就得到了一个O(n2)的算法。
注意到Sp是随着p的增加而递增的,观察两个集合:
Bomb[p]={p}∪{i |i>p Wi<=Sp-1}
Bomb[p-1]={p-1}∪{i |i>p-1 Wi<=Sp-2}
因为Sp-2<=Sp-1,所以{i |i>p-1 Wi<=Sp-2}Bomb[p]。
也就是说,Bomb[p-1]是在Bomb[p]的基础上,通过以下操作得到的:
1. 删除所有的i∈Bomb[p],Wi>Sp-2。
2. 添加p-1。
针对这两种操作,我们可以使用大根堆(Heap)。从大到小枚举p,对于每一个p,执行:
Step1. 如果堆的根>Sp-1,那么删除根;否则转Step3。
Step2. 转Step1。
Step3. 添加p,同时更新答案。
struct node { int w,id; node(){} node(int _w,int _id) { w=_w; id=_id; } }; const int INF=1000000000; const int MAX=15005; int n,W[MAX],L[MAX],P[MAX],aSize; int S[MAX]; node a[MAX<<2]; void down(int t) { int L,R,k; while(1) { L=t*2; R=t*2+1; if(L<=aSize&&a[L].w>a[t].w) k=L; else k=t; if(R<=aSize&&a[R].w>a[k].w) k=R; if(k==t) break; swap(a[k],a[t]); t=k; } } void up(int t) { int p=t/2; while(p>=1) { if(a[p].w<a[t].w) swap(a[p],a[t]); else break; t=p; p=t/2; } } int cmp(node a,node b) { return a.id<b.id; } int main() { RD(n); int i; FOR1(i,n) { RD(W[i],L[i],P[i]); S[i]=S[i-1]+W[i]; W[i]=S[i]-L[i]; } aSize=0; int ans=INF,p,cur=0; DOW1(p,n) { while(aSize>0&&a[1].w>S[p-1]) { cur-=P[a[1].id]; a[1]=a[aSize--]; down(1); } a[++aSize]=node(W[p],p); up(aSize); cur+=P[p]; ans=min(ans,cur); } aSize=0; int tempAns=INF; cur=0; DOW1(p,n) { while(aSize>0&&a[1].w>S[p-1]) { cur-=P[a[1].id]; a[1]=a[aSize--]; down(1); } a[++aSize]=node(W[p],p); up(aSize); cur+=P[p]; tempAns=min(tempAns,cur); if(tempAns==ans) break; } sort(a+1,a+aSize+1,cmp); FOR1(i,aSize) PR(a[i].id); return 0; }