CF533A Berland Miners
一、题目
有 \(n\) 个点和 \(m\) 个矿工,点构成以 \(1\) 为根的树形结构,所有矿工初始都在从 \(1\),如果他最后在 \(i\) 号点工作那么需要满足 \(1\) 到 \(i\) 路径上所有点的高度都大于等于矿工的高度。
你需要把每个矿工安排到一个点里面,最后每个点里最多一个人。你可以选择增高至多一个点的高度,如果有解输出最小增加高度,否则输出 -1
\(m\leq n\leq 5\cdot10^5\)
二、解法
设 \(mn[i]\) 表示根到 \(i\) 的最小高度,\(b[i]\) 表示从小到大排序后第 \(i\) 个矿工的高度。那么问题转化成这两个数组的匹配问题,设 \(num[i]\) 表示排序后第 \(i\) 个矿工能进的山洞数量,那么可以匹配完的充要条件是:
\[\forall i,num[i]\geq m-i+1
\]
多个条件的问题可以考虑转化成最值问题,这里我们维护 \(num[i]-(m-i+1)\) 的最小值。
再考虑增加点 \(x\) 高度对 \(num\) 的影响,我们只考虑路径上 \(x\) 是最小值的点 \(y\) 暴力修改,新的最小值可以通过增高后的值和次小值比较得到,那么会影响到一段前缀,用线段树问题区间修改和整体最小值即可。
再考虑增加到的高度是可以确定的,就是最大不合法 \(i\) 的 \(b_i\)(显然充分必要),通过均摊分析可以获得时间复杂度为 \(O(n\log n)\)
三、总结
维护多个同类型限制条件转维护最值。
一开始我觉得暴力修改的复杂度很假,你可以尝试缩小暴力的范围,然后均摊来降低复杂度。
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
const int M = 500005;
const int inf = 0x3f3f3f3f;
#define pii pair<int,int>
#define mp make_pair
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,zxy,tot,f[M],a[M],b[M],mn[M],cm[M];
int tr[4*M],fl[4*M];set<pii> s;vector<int> p[M];
struct edge
{
int v,next;
}e[2*M];
void dfs(int u,int fa)
{
s.insert(mp(a[u],u));
auto it=s.begin();
mn[u]=it->first;
p[it->second].push_back(u);
if(s.size()>1) it++,cm[u]=it->first;
else cm[u]=inf;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
dfs(v,u);
}
s.erase(mp(a[u],u));
}
void down(int i)
{
if(!fl[i]) return ;
tr[i<<1]+=fl[i];
tr[i<<1|1]+=fl[i];
fl[i<<1]+=fl[i];
fl[i<<1|1]+=fl[i];
fl[i]=0;
}
void ins(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
tr[i]+=c;fl[i]+=c;
return ;
}
int mid=(l+r)>>1;down(i);
ins(i<<1,l,mid,L,R,c);
ins(i<<1|1,mid+1,r,L,R,c);
tr[i]=min(tr[i<<1],tr[i<<1|1]);
}
signed main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[++tot]=edge{u,f[v]},f[v]=tot;
e[++tot]=edge{v,f[u]},f[u]=tot;
}
m=read();
for(int i=1;i<=m;i++) b[i]=read();
dfs(1,0);
//initailize for num
sort(mn+1,mn+1+n);
sort(b+1,b+1+m);
for(int i=1,j=1;i<=m;i++)
{
while(j<=n && mn[j]<b[i]) j++;
int num=n-j+1-(m-i+1);
if(num<0) zxy=b[i];
ins(1,1,m,i,i,num);
}
if(!zxy)//no need to modify
{
puts("0");
return 0;
}
int ans=inf;
for(int i=1;i<=n;i++)//modify it
{
if(a[i]>zxy) continue;
vector<pii> bk;
for(auto x:p[i])
{
int o=min(zxy,cm[x]);
int id=upper_bound(b+1,b+1+m,a[i])-b-1;
ins(1,1,m,1,id,-1);
bk.push_back(mp(id,1));
id=upper_bound(b+1,b+1+m,o)-b-1;
ins(1,1,m,1,id,1);
bk.push_back(mp(id,-1));
}
if(tr[1]>=0) ans=min(ans,zxy-a[i]);
while(!bk.empty())
{
pii t=bk.back();bk.pop_back();
ins(1,1,m,1,t.first,t.second);
}
}
if(ans>=inf) puts("-1");
else printf("%d\n",ans);
}