P7600 [APIO2021] 封闭道路
WrongAnswer_90 · · 题解
P7600 [APIO2021] 封闭道路
更好的阅读体验
APIO 从 CF 搬的题,模拟赛又搬了一遍/jy。
首先考虑暴力怎么做,即做
对于一个点
删去
考虑如何优化,观察题目中的限制,度数是一个比较特殊的东西,手玩几组数据可以发现对于一个度数较小的节点,在
记当前度数限制为
但是这样可能会出现一个点变成了无用点,它的父亲是有用点,儿子也是有用点,此时我们不需要 DP 它,但是需要 DP 它的父亲和儿子。
考虑删掉一些无用点后会形成若干个连通块,在对它的父亲所在连通块 DP 时可以把它直接看成叶子,而对于它的儿子所在连通块 DP 是可以把也它看成叶子,这样一个点可能有多个父亲,但是由于它是无用的,它的
图大概是这样的:
其中
暴力计算是不对的,因为我们上面只保证了 DP 有用点点数的正确性,如果对于每个有用点都扫一遍连接它的所有无用点,那一个菊花就噶了。
但是发现 DP 连接有用点和有用点的边的总个数也是线性的,刚刚复杂度假掉的原因是计算了有用点和无用点的边所以我们考虑用一个数据结构维护一个点的所有
理一下思路,我们需要支持加数(无用点对于有用点以及有用点的有用点儿子对他自己的
代码也不太好写,有注释,一不小心复杂度就假了。
struct Heap
{
priority_queue<int> q1,q2;
int sum;
inline void add(int x){q1.e(x),sum+=x;}
inline void del(int x){q2.e(x),sum-=x;}
inline void update(){while(!q1.empty()&&!q2.empty()&&q1.top()==q2.top())q1.pop(),q2.pop();}
inline int top(){return update(),q1.top();}
inline void pop(){sum-=top(),q1.pop();}
inline int size(){return q1.size()-q2.size();}
}a[250001];//可删堆
vector<pii> T[250001];
vector<int> ins,ers,ans;
int pos,dg,vis[250001],f[250001][2],n,deg[250001],p[250001];
bool cmp(pii x,pii y){return deg[x.fi]>deg[y.fi];}
bool cmp2(int x,int y){return deg[x]<deg[y];}
inline void del(int k){for(auto [to,v]:T[k])if(deg[to]<=dg)break;else a[to].add(v);}//删除无用点是把它的 $cost$ 即边权 $v$ 存到它旁边的所有有用点的堆里
inline void dfs(int k,int fa)
{
int del=deg[k]-(vis[k]=dg);//需要删的边数
while(a[k].size()>del)a[k].pop();//删的边一定越来越少,堆里存太多无用点也是没用的,只保留cost最小的
for(auto [to,v]:T[k])if(to!=fa){if(deg[to]<=dg)break;else dfs(to,k);}//按度数排序后只DP有用点
ins.clear(),ers.clear();
//ins 存的有用点儿子的所有 cost,在最后把它删掉
//在找前 del 大的时候需要弹堆知道 siz=del,可能会弹出一些需要保留的无用点的 cost
//ers 存的不小心被弹出的无用点的 cost,它还是有用的
int sum=0;
for(auto [to,v]:T[k])
{
if(to==fa)continue;
if(deg[to]<=dg)break;
int val=f[to][0]+v-f[to][1];sum+=f[to][1];//val 即 cost
if(val<=0)ans[dg]+=val,--del;
//有时删边比留边优秀,而对于删边是没有限制的,可以把它连接的边全删了即把 del 减成负数,要加特判,否则 WA on test 4
else a[k].add(val),ins.eb(val);
}
while(a[k].size()&&a[k].size()>del)ers.eb(a[k].top()),a[k].pop();//删 deg-x 条
f[k][1]=sum+a[k].sum;
while(a[k].size()&&a[k].size()>=del)ers.eb(a[k].top()),a[k].pop();//删 deg-x-1 条
f[k][0]=sum+a[k].sum;
for(int x:ers)a[k].add(x);
for(int x:ins)a[k].del(x);
}
vector<int> minimum_closure_costs(signed N,vector<signed> U,vector<signed> V,vector<signed> W)
{
n=N,ans.resize(n);int x,y,z;
for(int i=1;i<n;++i)x=U[i-1]+1,y=V[i-1]+1,z=W[i-1],ans[0]+=z,++deg[x],++deg[y],T[x].eb(mp(y,z)),T[y].eb(mp(x,z));
for(int i=1;i<=n;++i)sort(T[i].begin(),T[i].end(),cmp),p[i]=i;//把边按 deg 排序保证复杂度
sort(p+1,p+1+n,cmp2);//把点按 deg 排序保证复杂度
for(dg=1,pos=1;dg<n;++dg)
{
while(pos<=n&°[p[pos]]<=dg)del(p[pos++]);//p_pos 变成无用点
if(pos>n)break;
for(int j=pos;j<=n;++j)if(vis[p[j]]!=dg)dfs(p[j],0),ans[dg]+=f[p[j]][1];
}
return ans;
}