题解 【ARC115F Migration】

command_block

2021-10-27 17:33:39

Solution

**题意** : 给出一棵 $n$ 个节点的树,点有点权 $h_u$ 。 树上有 $k$ 块石子,记第 $i$ 块石子的位置为 $u_i$ ,则当前局面的“不稳定度”定义为 $\sum_{i=1}^kh_{u_i}$ 。 给出石子的初始位置,你的目标是将第 $i$ 个石子移动到节点 $t_i$ ,每次只能将一个石子移到相邻的节点(允许多个石子放在一个节点上),最小化过程中不稳定度的最大值。 $n\leq 2000$ ,时限$\texttt{4s}$。 ------------ 考虑二分。 记 $S$ 为初始局面,$T$ 为终止局面。 记 $H(S)$ 为局面 $S$ 的不稳定度(若不稳定度相同则比较字典序), $f(S)$ 为局面 $S$ 所能到达(有 $mid$ 约束)的最小的 $H(V)$。 不难发现,由于操作的可逆性,$S$ 能到达 $T$ 当且仅当两者能到达的不稳定度最小的局面相同。 问题转化为求 $f(S)$ 。 ------------ 根据操作的可逆性,若 $S,S'$ 能互相到达,则 $f(S)=f(S')$ 。 那么,我们每次找出一个可达的 $S'$ 使得 $H(S')<H(S)$ ,重复若干次即可得到最小的 $H$ 。 为了便于维护,我们每次只考虑一个石子的连续移动,将这个石子(合法地)移动到一个 $H$ 更小的位置。 可以证明,若不存在这种方案,则不存在任意 $S'$ 使得 $H(S')<H(S)$。于是只用考虑这种移动就好。 显然这样的移动最多 $O(nk)$ 次。 我们需要快速求解 : 从 $u$ 点移动,所到的点权不超过 $L$ ,能到达的最小点权。特殊地,若点权不能变小,则置为 $+\infty$ 。这个随便 dfs 预处理。 然后再用堆维护每个石子的最小出边即可。 ------------ 可以发现其实不用二分,只需每次挑一步最大不稳定度最小的转移。 记 ${\rm maxh}(u,v)$ 为路径 $(u,v)$ 的最大权值。 这样,我们对每个 $u$ 记 $t_u$ 表示 $v$ 点满足 $h_v<h_u$ 且 ${\rm maxh}(u,t_u)$ 最小(若相同,则比较 $v$ 的权值)。 当我们移动 $u$ 点时,必然会前往 $t_u$ 。 这样的复杂度为 $O(nk\log k)$ ,已经足以通过。 ------------ $t$ 的求法:考虑按 $h_u$ 从小到大加入点的过程,建立类克鲁斯卡尔重构树。然后再次从小到大考虑 $h_u$ 目标就是找到 $u$ 与更小的点的 $\rm LCA$ ,容易倍增求算。复杂度为 $O(n\log n)$。 建立一张新图,对于 $u$ ,连有向边 $u\rightarrow t_u$ ,边权为 $c_u={\rm maxh}(u,t_u)-h_u$ (即不稳定度的增量)。这形成一棵内向树。 贪心时,每次走 $c_u$ 最小的边。 - **性质** :若 $t_a=b,t_b=c$ ,则有 $c_a\le c_b$ 。也就是说,在新树上越浅边权越大。 **证明** :$a,b,c$ 的分布如下图:(其他一些情况可以视为该图的退化) ![](https://cdn.luogu.com.cn/upload/image_hosting/pmebay2r.png) 记 $A={\rm maxh}(a,t),B={\rm maxh}(b,t),C={\rm maxh}(c,t)$ 。 若 $A$ 最大或 $B$ 最大:此时 ${\rm maxh}(a,b)\geq {\rm maxh}(a,c)$ ,$t_a$ 选择 $c$ ,矛盾。 因此只可能是 $C$ 最大,则 ${\rm maxh}(a,b)=\max(A,B)\le \max(B,C)={\rm maxh}(b,c)$ . ${\rm maxh}(a,b)\le {\rm maxh}(b,c)$ 结合 $h_a\ge h_b$ 则有 $c_a\le c_b$ 。 于是,我们可以直接给边按 $c$ 从小到大排个序(也是从浅往深,若权值相同则比较深度),然后依次移动就是。 这里我们可能同时移动多个石子,用 xor Hash 维护集合特征,并记一个集合大小以计算不稳定度的变化。 可以把二分加回来以简化实现。 复杂度 $O(n\log n)$。 ```cpp #include<algorithm> #include<cstdio> #include<vector> #define ll long long #define pb push_back #define MaxN 2050 using namespace std; struct UFS{ int f[MaxN]; void Init(int n) {for (int i=1;i<=n;i++)f[i]=i;} int find(int u) {return f[u]==u ? u : f[u]=find(f[u]);} void merge(int u,int v) {f[find(u)]=find(v);} }F; int n,k,h[MaxN],p[MaxN],u1[MaxN],u2[MaxN],rnd[MaxN],o1[MaxN],o2[MaxN]; bool cmp(int A,int B){return h[A]<h[B];} vector<int> g[MaxN]; int f[12][MaxN],vist[MaxN],t[MaxN],c[MaxN],dep[MaxN]; int up(int u) { if (vist[u])return u; for (int k=10;k>=0;k--) if (!vist[f[k][u]])u=f[k][u]; return f[0][u]; } bool cmp2(int A,int B){return c[A]==c[B] ? dep[A]>dep[B] : c[A]<c[B];} const int INF=1000000000; int cnt[MaxN]; void solve(int *u,int *o,ll lim) { ll s=0; for (int i=1;i<=n;i++)o[i]=cnt[i]=0; for (int i=1;i<=k;i++){ s+=h[u[i]]; o[u[i]]^=rnd[i]; cnt[u[i]]++; } for (int i=1;i<n;i++){ int u=p[i],v=t[u]; if (!cnt[u])continue; if (s+c[u]>lim)break; o[v]^=o[u];o[u]=0; s+=1ll*cnt[u]*(h[v]-h[u]); cnt[v]+=cnt[u];cnt[u]=0; } } bool chk(ll lim) { solve(u1,o1,lim);solve(u2,o2,lim); for (int i=1;i<=n;i++)if (o1[i]!=o2[i])return 0; return 1; } int main() { scanf("%d",&n);F.Init(n); for (int i=1;i<=n;i++)scanf("%d",&h[p[i]=i]); sort(p+1,p+n+1,cmp); for (int i=1,u,v;i<n;i++){ scanf("%d%d",&u,&v); g[u].pb(v);g[v].pb(u); } for (int j=1;j<=n;j++){ int u=p[j];vist[u]=1; for (int i=0;i<g[u].size();i++){ int v=F.find(g[u][i]); if (u==v||!vist[v])continue; f[0][v]=u;F.merge(v,u); } } for (int j=1;j<=10;j++) for (int i=1;i<=n;i++) f[j][i]=f[j-1][f[j-1][i]]; for (int i=1;i<=n;i++)vist[i]=0; for (int i=1;i<=n;i++){ int u=p[i]; if (i>1){ int v=up(u); t[u]=vist[v]; c[u]=h[v]-h[u]; }else c[u]=INF; for (int v=u;!vist[v];v=f[0][v])vist[v]=u; } for (int j=2;j<=n;j++) dep[p[j]]=dep[t[p[j]]]+1; sort(p+1,p+n+1,cmp2); scanf("%d",&k); ll s1=0,s2=0; for (int i=1;i<=k;i++){ scanf("%d%d",&u1[i],&u2[i]); rnd[i]=rand()<<15^rand(); s1+=h[u1[i]];s2+=h[u2[i]]; } ll l=max(s1,s2),r=1ll<<45,mid; while(l<r){ mid=(l+r)>>1; if (chk(mid))r=mid; else l=mid+1; }printf("%lld",r); return 0; } ```