P5021 赛道修建
题意:
- 给你一珂树。
- 让你找出一些路径使得最小的路径长度的最大。
- 路径要求边不相交,点可以相交。
\(80\) 分前置做法
首先考虑二分。假设二分的值是 \(goal\)。
left:=0; root:=1;
while left<=right do
begin
fillchar(val,sizeof(val),0); // val 数组代表每一个点能给父亲的最优贡献
mid:=(left+right) >> 1;
tmp:=Check(mid,root,root);
if tmp>=m then begin left:=mid+1; ans:=mid end else right:=mid-1;
end;
writeln(ans);
我们考虑到,一个节点的儿子可能会给自己带来一些路径,把这些路径存起来,设为 \(rope\) 数组。
在 \(rope\) 中,如果有一条路径 \(\ge goal\) 的话可以自己匹配,\(ans++\)。如果不能自己匹配,那么我们就把它跟别人匹配,如果可以那么 \(ans++\)。因为时间的限制,上面的操作时可以先排一个序然后操作的。
(\(11\) 直接选,而 \(9\) 和 \(1\) 加起来也可以 \(\ge goal\))
- 问题 \(1\) 为什么要两两匹配?
一个树上路径就是一条线,而一条线经过一个点只能连出另一条边,不能连多条边。
- 问题 \(2\) 为什么 \(\ge goal\) 可以直接选?
它跟别人再次匹配就是葬送了别人,所以最好自个匹配掉。
function Check(mid,x,fa:longint):longint;
var i,l:longint;
begin
i:=cnt[x]; Check:=0;
while i<>-1 do // 先跑儿子
begin
if reach[i]<>fa then inc(Check,Check(mid,reach[i],x));
i:=next[i];
end;
i:=cnt[x]; tail:=0; // 把所有路径取下来,其中如果儿子有路径升上来,那么久连着边一起加上来
while i<>-1 do
begin
if reach[i]<>fa then
begin
inc(tail);
rope[tail]:=val[reach[i]]+value[i];
end;
i:=next[i];
end;
Sort(1,tail); // 手动
for i:=tail downto 1 do
if rope[i]>=mid then begin inc(Check); dec(tail); end else break;
// 自己直接匹配,注意这里有单调性所以可以直接 break
l:=1;
for i:=tail downto 1 do // 互相匹配,如果两两加起来可以的话就弄
begin
while (rope[i]+rope[l]<mid)and(l<tail+1) do inc(l); // 用指针搞
if l<i then begin inc(Check); rope[i]:=0; rope[l]:=0; end else break;
// 匹配过就不要了 rope[i/l]=0
end;
for i:=tail downto 1 do val[x]:=max(val[x],rope[i]);
// 找最大的(且没有被匹配过)给父亲
exit(check);
end;
\(100\) 做法
看完上面那个你会发现你得到了 \(80\) 的高分部分分。
错误原因在于 你留给父亲的只是你那些边匹配剩下的,对你的父亲很不利。所以 \(80\) 就是贪心。
首先先把那些 \(\ge goal\) 的匹配掉,原因如上述。
其余的就进行一次二分答案看留哪个路径给父亲好,如果我留了这条边我还能保证自己的答案贡献不变,那很好,其它的给父亲就 \(OK\)。
为了方便(X\(i\)n\(Y\)a\(n\)g),我们定义一个 \(Judge(goal,choose,tail)\) 代表在 \(1\)~\(tail\) 内不匹配 \(rope[choose]\) 达到 \(goal\) 的最大匹配数量。满足单调性很好搞。
function Judge(goal,choose,tail:longint):longint;
var l,r:longint;
begin
l:=0; Judge:=0;
for r:=tail downto 1 do
begin
if r=choose then continue;
if l=choose then inc(l);
repeat
inc(l); if l=choose then inc(l);
until (rope[r]+rope[l]>=goal)or(l>tail);
if l<r then inc(Judge) else break;
end;
end;
然后看一下 \(Check\):
function Check(goal,x,fa:longint):longint;
var i,l,ans,mid,tmp:longint;
begin
i:=cnt[x]; Check:=0;
while i<>-1 do
begin
if reach[i]<>fa then inc(Check,Check(goal,reach[i],x));
i:=next[i];
end;
i:=cnt[x]; tail:=0;
while i<>-1 do
begin
if reach[i]<>fa then
begin
inc(tail);
rope[tail]:=val[reach[i]]+value[i];
end;
i:=next[i];
end;
Sort(1,tail);
for i:=tail downto 1 do
if rope[i]>=goal then begin inc(Check); dec(tail); end else break;
// 单独的匹配掉
tmp:=Judge(goal,-1,tail); // 全部都选可以匹配多少
inc(Check,tmp); // 答案+=tmp
l:=1; r:=tail; ans:=0;
while l<=r do
begin
mid:=(l+r) >> 1; // 我留 rope[mid] 给父亲
if Judge(goal,mid,tail)=tmp then begin l:=mid+1; ans:=rope[mid]; end else r:=mid-1;
// 坑点注意:由于可能没有路径父亲,所以这里的 ans 不能存 mid,只能存 rope[mid]
end;
val[x]:=ans;
end;
非正确的复杂度分析
设 \(\sqrt{N}\) 个节点每一个节点有 \(\sqrt{N}\) 个儿子(平摊?)。
- 排序 \(+\) 各种 \(N \times \sqrt{N} \log \sqrt{N}\)
- 外面一层是 \(\log \sum\limits^{N}_{i=1}l_i\) 。
所以应该是 \(N \sqrt{N} \log \sqrt{N} \log \sum\limits^{N}_{i=1}l_i\)。
按照大家说的 \(N \log N \log \frac{\sum\limits^{N}_{i=1}l_i}{M}\) 就好了。