[二分图匹配][各种优化][BZOJ 2034]最大收益

时间:2022-03-19 06:12:31

Description

给出N件单位时间任务,对于第i件任务,如果要完成该任务,需要占用[Si, Ti]间的某个时刻,且完成后会有Vi的收益。求最大收益。 N≤5000,1 ≤ Si ≤ Ti ≤ 108,1 ≤ Vi ≤ 108。 

Analysis

看到题目的第一眼会想到区间DP。。但是这数据范围过于凶残...

第二想法就想到了二分图匹配。左边的节点代表任务,右边的节点代表时刻。但是好像和DP一样,数据范围承受不起...

接着仔细的分析一下,发现右边时刻有许多都是用不到的,最后会用到的,最多只有N个节点!!

我们应该可以通过某种策略找到这N个节点。

最开始的时候,数轴上每一个节点都是可行的,对于每一个节点,我们可以找到在它的时刻范围内最小的且仍然可行的一个点,并添加到右边集合内。

可以证明,利用这N个节点最后构造出的方案就是一种最优方案。(这个证明不会...难道是数学归纳法么?)

接下来我们就可以将S数组排序,线性的扫描出这N个节点。

观察得出右端点集合中,所有时刻是递增的(这个之后会用到)。

然后我们可以通过KM算法来做?

O(N^3)?还是不行!

我们还要继续优化。

我们将任务按价值从大到小排序,依次枚举,若是可以构成匹配则加入ans中,不能则舍去。

上面的那个结论YY一下应该是显然的。

于是我们可以不用做二分图最大权匹配,只用作二分图匹配,匈牙利即可

但是效率仍然是蛋疼的O(N^3)...

考虑继续优化。

刚才我们发现了右端点集合中,所有时刻是递增的。

进而可以得出,每个左端点连向的右端点是一段连续的!

于是我们可以采取一种策略,对于当前的任务,将它之前匹配的任务都尽量往后匹配。

这样每次匹配的效率降到了O(N),总效率O(N^2),可以通过了。

具体实现见程序。

var
  all,n,m,i,j,last,now:longint;
  ans:int64;
  st,ed,v,link,re,b:array[1..5010] of longint;
procedure qsort1(l,r:longint);
var
  i,j,mid,t,mid2:longint;
begin
  i:=l; j:=r; mid:=st[(l+r) shr 1]; mid2:=ed[(l+r) shr 1];
  repeat
    while (st[i]<mid) or (st[i]=mid) and (ed[i]<mid2) do inc(i);
    while (st[j]>mid) or (st[j]=mid) and (ed[j]>mid2) do dec(j);
    if i<=j then begin
      t:=st[i]; st[i]:=st[j]; st[j]:=t;
      t:=ed[i]; ed[i]:=ed[j]; ed[j]:=t;
      t:=v[i]; v[i]:=v[j]; v[j]:=t;
      inc(i); dec(j);
    end;
  until i>j;
  if i<r then qsort1(i,r);
  if j>l then qsort1(l,j);
end;
procedure qsort2(l,r:longint);
var
  i,j,mid,t:longint;
begin
  i:=l; j:=r; mid:=v[(l+r) shr 1];
  repeat
    while v[i]>mid do inc(i);
    while v[j]<mid do dec(j);
    if i<=j then begin
      t:=st[i]; st[i]:=st[j]; st[j]:=t;
      t:=ed[i]; ed[i]:=ed[j]; ed[j]:=t;
      t:=v[i]; v[i]:=v[j]; v[j]:=t;
      inc(i); dec(j);
    end;
  until i>j;
  if i<r then qsort2(i,r);
  if j>l then qsort2(l,j);
end;
function find(i,x:longint):boolean;  //贪心的匹配
var
  j:longint;
begin
  if b[x]>ed[i] then exit(false);
  if link[x]=0 then begin
    link[x]:=i;
    exit(true);
  end;
  j:=link[x];
  if ed[i]>ed[j] then exit(find(i,x+1))
  else begin
    if find(j,x+1) then begin
      link[x]:=i;
      exit(true);
    end;
  end;
  exit(false);
end;
begin
  readln(n);
  for i:=1 to n do readln(st[i],ed[i],v[i]);
  qsort1(1,n);
  last:=0;
  for i:=1 to n do begin
    inc(last);
    if st[i]>last then last:=st[i];
    inc(m); b[m]:=last;
  end;
  qsort2(1,n);
  b[m+1]:=maxlongint;
  for i:=1 to n do begin
    for j:=1 to m do
      if (st[i]<=b[j]) and (ed[i]>=b[j]) then break;
    if find(i,j) then inc(ans,v[i]);
  end;
  writeln(ans);
end.