首先考虑如下容易想到的贪心方法:
令 f i f_i fi 为某一颗个棋子 i i i 下一步要经过的点的点权。
对于每一个点,我们考虑它到终点的路径上经过的下一个点,我们想要答案尽可能小,那么对于下一步的局面,肯定是移动 f i f_i fi 最小的棋子最优。但显而易见,这样的贪心方法是错误的,因为我们并没有考虑以后的每一个局面是否最优。
考虑动态规划?比赛结束后,发现它仍然可以用贪心来做,只要保证贪心的正确性就可以了。
根据上面的贪心方法,我们可以这样考虑:
- 如果对于某一个点 i i i,我们将其移动到比它自身点权要小的一个点上,那么此时对于这个操作后的下一步操作,相较于移动当前尚未移动棋子的情况,其总潜能的最大值是不会变大的。那么我们只要考虑在移动的过程中,保证潜能增大的量最小就可以了,这样的移动方式这样可以满足最大值最小。
- 而考虑中间的移动过程,对于一次抉择过程,假设我们选择了一颗棋子可以使潜能增大的量最小,即路径中经过的点权最大值最小,那么优先移动这颗棋子所造成的的潜能增大量一定会比优先移动其他棋子的潜能增大量要小,因为这颗棋子在走到终点后点权变小的过程中,其路径上的潜能增大量相较于其他棋子也是更小的,而后再移动其他棋子增大量也会基于这个最小增大量,那么优先移动这颗棋子一定是最优的且不会有更优于它的棋子。这样往后多考虑一些点而不止一个的方法,正确性便可得以证明。
思考做法。我们对于任意一颗棋子,如果直接考虑到达终点行动轨迹,似乎有些难想。但是,棋子一定能够有路径从起点走到终点,那我们在终点设置一颗虚拟的棋子,让它也依照上面的贪心方法移动,最终一定能够让两颗棋子相遇,这样就能够形成一条完整的路径,那么它就是答案路径。面对两颗棋子可能多次相遇,意味着后面有更优的点使得潜能更小,那么我们选择它们最后一次相遇的点作为两条轨迹的连接点即可。
具体做法如下:
设
f
i
f_i
fi 为要跳到的比自身点权小的点,
w
i
w_i
wi 为跳过去的路径中点权最大值的最小值,
s
u
m
sum
sum 为一开始尚未移动棋子的局面的潜能。那么跳过去之后当前潜能将变为
s
u
m
−
h
i
+
h
f
i
sum-h_i+h_{f_i}
sum−hi+hfi,而在移动过程中潜能最大值为
s
u
m
−
h
i
+
w
i
sum-h_i+w_i
sum−hi+wi。首先用 dfs 找出每一个点往后的路径的点权最大值得到
w
i
w_i
wi。接着我们用两个优先队列,一个为真实棋子队列 qs
,一个为虚拟棋子队列 qt
,分别将真实棋子和虚拟棋子的
w
i
w_i
wi 值放进去,最优策略显然是移动当前
w
i
w_i
wi 值最小的棋子,简单分为下面四种情况:
- 队列
qs
为空,此时qt
中的虚拟棋子进行移动 - 队列
qt
为空,此时qs
中的真实棋子进行移动 -
s
u
m
s
sums
sums 减去
qs
队列中 w i w_i wi 值最小的棋子小于 s u m t sumt sumt 减去qt
队列中 w i w_i wi 值最小的棋子,此时移动qs
队列中的棋子更优 - 反之于第三种情况,此时移动
qt
队列中的棋子更优。
按照上述贪心方法对棋子进行移动,直到所有对应真实棋子和虚拟棋子相遇为止,最终的答案即为潜能最大值的最小值。时间复杂度 O ( n log n ) O(n\log n) O(nlogn)。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,u,v,p[5000],head[5000],cnt,k,s[5000],t[5000];
ll mx[5000],f[5000],w[5000],ss,st,dif,ans;
//f是要跳到的比自身点权小的点,w是跳过去的路径中点权最大值的最小值
priority_queue<pair<ll,ll> >qs,qt;
struct node
{
ll to,nxt;
}e[5000];
void add(ll x,ll y)
{
e[++cnt].to=y;
e[cnt].nxt=head[x];
head[x]=cnt;
}
void dfs(ll x,ll fa)
{
mx[x]=max(p[x],mx[fa]);
for(int i=head[x];i;i=e[i].nxt)
{
ll y=e[i].to;
if(y!=fa) dfs(y,x);
}
}
void works()
{
ll x=qs.top().second;
qs.pop();
ans=max(ans,ss+w[s[x]]);
ss-=p[s[x]];
if(s[x]!=t[x]) dif--;
s[x]=f[s[x]];
ss+=p[s[x]];
if(s[x]!=t[x]) dif++;
if(f[s[x]]) qs.push({-w[s[x]],x});
}
void workt()
{
ll x=qt.top().second;
qt.pop();
ans=max(ans,st+w[t[x]]);
st-=p[t[x]];
if(s[x]!=t[x]) dif--;
t[x]=f[t[x]];
st+=p[t[x]];
if(s[x]!=t[x]) dif++;
if(f[t[x]]) qt.push({-w[t[x]],x});
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i];
for(int i=1;i<n;i++)
{
cin>>u>>v;
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
{
dfs(i,0);
for(int j=1;j<=n;j++)
{
if(p[i]>p[j]||(p[i]==p[j]&&j<i))
{
if(!f[i]||mx[j]<w[i]||(mx[j]==w[i]&&j<f[i]))
{
f[i]=j;
w[i]=mx[j];
}
}
}
w[i]-=p[i];
}
cin>>k;
for(int i=1;i<=k;i++)
{
cin>>s[i]>>t[i];
ss+=p[s[i]],st+=p[t[i]];
if(f[s[i]]) qs.push({-w[s[i]],i});
if(f[t[i]]) qt.push({-w[t[i]],i});
if(s[i]!=t[i]) dif++;
}
ans=max(ss,st);
while(dif)
{
if(qs.empty()) workt();
else if(qt.empty()) works();
else if(ss-qs.top().first<st-qt.top().first) works();
else workt();
}
cout<<ans;
return 0;
}