解题报告 B_station
1. 题目
B_station
在离著名的国家Berland不远的地方,有一个水下工作站。这个工作站有N层。已知:是第i层装有Wi的水,最多可以容纳Li的水,恐怖分子炸毁第i层的代价是Pi。第i层一旦被炸毁,该层所有的水都将倾泻到第i+1层。如果某一层的水量超过了它的容量(即Li),那么该层就将自动被毁坏,所有的水也会倾泻到下一层。
Pivland的恐怖分子想要用最少的钱毁掉第N层,现在他雇佣你来计算,需要炸毁哪些层。
输入:第一行有一个自然数N(1<=n<=15000)。接下来的N行,每行3个整数Wi, Li, Pi(0<=Wi,Li,Pi<=15000)。
输出:输出需要炸毁的层的编号。
样例Input 样例output
3 1
1000 1000 1 2
0 1000 2
2 10 100
2. 题目实质
这个题讲的也很明白。
3. 算法
首先,不要一看见最优值就认为是动规,这个题找状态......
其实,他是一个枚举。
选择枚举对像:不妨设恐怖分子炸毁的最高层是第p层(第一层是最高层,第N层是最底层)。
因为恐怖分子的目标是毁灭第N层,所以水必须从第p层一直泻下去。如果存在一个i,满足Wp+Wp+1+…+Wi-1+Wi<=Li,也就是说前面几层的水全部泄下来也无法把第i层自动冲毁,那么就必须要使用炸药把它炸开了。
考察: Wp+Wp+1+…+Wi-1+Wi<=Li
这个式子要求一段的数字和,为了快速,用前缀和:
令Si=W1+W2+…+Wi(特别的S0=0)。
上式可表示成: Si-Sp-1 <=Li
所以恐怖分子需要炸毁的层的集合就是Bomb[p]={p}∪{i |i>p Si-Sp-1<=Li}
我们枚举p,然后看哪些层需要炸毁。这样就得到了一个O(n2)的算法。
然后,这里主要讲一下优化:
剪枝:在你枚举的时候,在内层循环中记录一下当前炸了这么多层已经花了多少钱,当这个数目大于已经记录的最小值时, break 。(这样就能过)
记忆化:当搜到了一层的时候,已经可以自己冲垮而不用花钱,那么显然将炸的最高层再往上抬,它还是不用花钱,此时就需要将它记录下来,以后不用搜了。也可以在每次需要花钱的时候,记录下还有多少水就可以不用花钱了,到 0 的时候也就不用再搜了。
单调性优化:(大根堆、二分查找、单调队列 etc)
Bomb[p]={p}∪{i |i>p Si-Sp-1<=Li} è
令Qi=Si-Li,那么:
Bomb[p]={p}∪{i |i>p Qi<=Sp-1}
注意到Sp是随着p的增加而递增的(单调性),观察两个集合:
Bomb[p]={p}∪{i |i>p Qi<=Sp-1}
Bomb[p-1]={p-1}∪{i |i>p-1 Qi<=Sp-2}
因为Sp-2<=Sp-1,所以{i |i>p-1 Qi<=Sp-2}包含于 Bomb[p],也就是说,Bomb[p-1]是在Bomb[p]的基础上,通过以下操作得到的:
删除所有的i∈Bomb[p],Qi>Sp-2。(所以找最大值,删除判断就行了)
添加p-1。
针对这两种操作,我们可以使用大根堆(Heap)(单调队列、二分查找什么的都行)。从大到小枚举p,对于每一个p,执行:
Step1. 如果堆的根i满足Qi >Sp-2,那么删除根;否则转Step3。
Step2. 转Step1。
Step3. 添加p-1。
每层至多进堆一次、出堆一次,所以总的时间复杂度是O(nlogn)。对于n<=15000的范围完全能够胜任了。
4. 注意事项
不要一看到最优值就写动规,也适当想一想基础算法。
注意观察数据范围,不要每次一上来就写最厉害(一般也是最难写)的优化,优化优化的不好就会变成退化了,像这个题,三个优化中有一个就可以 AC 。
5. 代码
只用了第一个优化,刚好能过(SueMiller)
var n:longint;
i,j,k:longint;
w,l,p:array[0..15001]of longint;
v,vv:array[0..15001]of boolean;
ans,temp,co:longint;
begin
assign(input,'station.in');reset(input);
assign(output,'station.out');rewrite(output);
fillchar(w,sizeof(w),0);
fillchar(l,sizeof(l),0);
fillchar(p,sizeof(p),0);
readln(n);
for i:=1 to n do
begin
readln(w[i],l[i],p[i]);
end;
if w[n]>l[n] then begin
writeln(0);
close(input);close(output);
halt;
end;
temp:=w[n];
k:=n;
while temp<=l[n] do
begin
dec(k);
temp:=temp+w[k];
end;
ans:=p[n];
fillchar(v,sizeof(v),false);
fillchar(vv,sizeof(vv),false);
vv[n]:=true;
for i:=k downto 1 do
begin
temp:=w[i];
if temp<=l[i] then
begin
co:=p[i];
v[i]:=true;
end;
for j:=i+1 to n do
begin
if temp+w[j]>l[j] then begin
temp:=temp+w[j];
continue
end
else begin
co:=co+p[j];
v[j]:=true;
temp:=temp+w[j];
if co>ans then begin
fillchar(v,sizeof(v),false);
break;
end;
end;
end;
if co<ans then
begin
vv:=v;
ans:=co;
fillchar(v,sizeof(v),false);
end;
end;
writeln(ans);
for i:=1 to n do
if vv[i] then write(i,' ');
writeln;
// temp:=0;
// for i:=1 to n do
// if vv[i] then begin
// temp:=temp+p[i];
// end;
// writeln(temp,' ##');
close(input);close(output);
end.
大根堆 (Liukeke)
program liukeke;
var
w,p,l,s,q:array[0..15001] of longint;
f:array[0..15001] of longint;
ans:array[0..15001] of longint;
m,ans0,anscost,cost,i,n:longint;
procedure downsift(x:longint);
var
k,temp:longint;
begin
temp:=f[x];
k:=x<<1;
while k<=m do
begin
if(k<m)and(q[f[k]]<q[f[k+1]]) then inc(k);
if q[temp]<q[f[k]] then
begin
f[x]:=f[k];
x:=k;
k:=x<<1;
end
else break;
end;
f[x]:=temp;
end;
procedure upsift(x:longint);
var
k,temp:longint;
begin
temp:=f[x];
k:=x>>1;
while k>0 do
begin
if q[temp]>q[f[k]] then
begin
f[x]:=f[k];
x:=k;
k:=x>>1;
end
else break;
end;
f[x]:=temp;
end;
procedure sort(l,r:longint);
var
i,j,mid,temp:longint;
begin
i:=l;j:=r;mid:=ans[(l+r)>>1];
repeat
while ans[i]<mid do inc(i);
while ans[j]>mid do dec(j);
if i<=j then
begin
temp:=ans[i];
ans[i]:=ans[j];
ans[j]:=temp;
inc(i);
dec(j);
end;
until i>j;
if l<j then sort(l,j);
if i<r then sort(i,r);
end;
begin
assign(input,'station.in');reset(input);
assign(output,'station.out');rewrite(output);
readln(n);
for i:=1 to n do
readln(w[i],l[i],p[i]);
for i:=1 to n do
s[i]:=s[i-1]+w[i];
for i:=1 to n do
q[i]:=s[i]-l[i];
m:=1;
f[1]:=n;
cost:=p[n];
anscost:=p[n];
for i:=n downto 2 do
begin
//
while (q[f[1]]>s[i-2])and(m>0) do
begin
dec(cost,p[f[1]]);
f[1]:=f[m];
dec(m);
downsift(1);
end;
//
inc(m);
f[m]:=i-1;
upsift(m);
//
inc(cost,p[i-1]);
if cost<anscost then
begin
anscost:=cost;
ans:=f;
ans0:=m;
end;
end;
//outit;
sort(1,ans0);
writeln(anscost);
for i:=1 to ans0 do
write(ans[i],' ');
close(input);
close(output);
end.