首页 > 其他分享 >P7600 [APIO2021] 封闭道路

P7600 [APIO2021] 封闭道路

时间:2023-10-14 10:35:18浏览次数:51  
标签:int 无用 封闭 有用 cost P7600 APIO2021 DP deg

P7600 [APIO2021] 封闭道路

APIO 从 CF 搬的题,模拟赛又搬了一遍/jy。

首先考虑暴力怎么做,即做 \(n\) 次树形 DP,设 \(f_{i,0}\) 表示强制删掉 \((i,fa_i)\) 这条边的最小代价,\(f_{i,1}\) 表示强制保留 \((i,fa_i)\) 这条边的最小代价。

对于一个点 \(u\),在限制度数为 \(x\) 时,对于 \(f_{i,0}\) 需要删 \(deg_u-x-1\) 个儿子,\(f_{i,1}\) 需要删 \(deg_u-x\) 个儿子。

删去 \((u,v)\) 的代价是 \(val+f_{v,0}\),不删的代价是 \(f_{v,1}\)。首先强制一条边都不删,然后我们选一些儿子用 \(cost=val+f_{v,0}-f_{v,1}\) 来替代,我们要选择最小的一些 \(cost\) 加到 \(f_u\) 里,需要排序,复杂度为 \(\mathcal O(n^2\log n)\)。

考虑如何优化,观察题目中的限制,度数是一个比较特殊的东西,手玩几组数据可以发现对于一个度数较小的节点,在 \(x>=deg_u\) 的时候,\(f_{u,0}\) 和 \(f_{u,1}\) 都选择了全部的儿子,没有进行替代操作,但对于每次 DP 它都需要计算一次,非常的呆,考虑删去这些节点,把这个节点的东西存在它相邻的有用的节点即度数 \(>x\) 的节点里。然后每次只 DP 度数 \(>x\)
的点,这样复杂度就是对的,因为 \(\sum deg=2n-2\),而一个点被 DP,当且仅当 \(x\in[0,deg]\),这样一个点最多被计算 \(deg\) 次。

记当前度数限制为 \(x\),设 \(deg_u\leq x\) 的点为无用点,\(deg_u>x\) 的点为有用点,我们对于当前的答案只需要 DP 有用点,对于已经无用的点 \(u\),它的 \(f_{u,0}\) 和 \(f_{u,1}\) 之间只差了一个 \((u,fa_u)\) 的权值(因为它不需要删任何子树内的边,只需要满足 DP 状态的限制),所以它的 \(cost\) 就是一条边权 \(val\)。

但是这样可能会出现一个点变成了无用点,它的父亲是有用点,儿子也是有用点,此时我们不需要 DP 它,但是需要 DP 它的父亲和儿子。

考虑删掉一些无用点后会形成若干个连通块,在对它的父亲所在连通块 DP 时可以把它直接看成叶子,而对于它的儿子所在连通块 DP 是可以把也它看成叶子,这样一个点可能有多个父亲,但是由于它是无用的,它的 \(cost\) 只和边权有关,删掉它的时候只考虑连接它的有用点并想办法存下它的 \(cost\) 即可。而对于无用点和无用点之间的边,我们一定会选择保留,所以这部分是不需要考虑的。

图大概是这样的:

image.png

其中 \(2,4\) 是无用点,剩下的都是有用点,对于一个红色的连通块,它的内部仍然先原来一样 DP,只不过它还连了一些无用点,这些无用点对它有一个 \(cost\) 的影响,即选择 \(cost\) 来替代是是不一定全需要从它的有用点子树里找,也可以选无用点,所以我们现在的问题是如何解决无用点对有用点的贡献。

暴力计算是不对的,因为我们上面只保证了 DP 有用点点数的正确性,如果对于每个有用点都扫一遍连接它的所有无用点,那一个菊花就噶了。

但是发现 DP 连接有用点和有用点的边的总个数也是线性的,刚刚复杂度假掉的原因是计算了有用点和无用点的边所以我们考虑用一个数据结构维护一个点的所有 \(cost\),然后从中选一些最小的。

理一下思路,我们需要支持加数(无用点对于有用点以及有用点的有用点儿子对他自己的 \(cost\)),删数(当前有用点的儿子可能在 \(x\) 时的 \(cost\) 和在 \(x+1\) 时不同,所以 DP 完一次要删掉一个有用点的儿子对他自己的 \(cost\)),可以用堆维护,这样总复杂度就是 \(\mathcal O(n\log n)\)。

代码也不太好写,有注释,一不小心复杂度就假了。

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&&deg[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;
}

标签:int,无用,封闭,有用,cost,P7600,APIO2021,DP,deg
From: https://www.cnblogs.com/WrongAnswer90-home/p/17763762.html

相关文章

  • OSCAR开源专访 | 企业内源最大的挑战在于改变封闭思维和竞争观念——智网创新中心张东
    开源作为一种开放的、无边界的新型协作模式,是数字经济创新、开放、共享、可持续发展的源头活水。开源的大获成功也启发不少企业将开源软件开发的经验教训应用到组织内部中来,是谓内源。当前内源建设已成为企业提升研发效率、释放产业效能的重要手段,在通信行业亦是如此,同时各项能力建......
  • O开放封闭原则OCP
    Open-ClosedPrinciple,OCP,对扩展开放,对修改关闭(设计模式的核心原则)定义一个软件实体(如类、模块和函数)应该对扩展开放,对修改关闭.意思是,在一个系统或者模块中,对于扩展是开放的,对于修改是关闭的,一个好的系统是在不修改源代码的情况下,可以扩展你的功能.而实现......
  • python实现"对修改封闭, 对扩展开放"
    对修改封闭,对扩展开放是架构设计的基本原则.意思是如果程序增加新的功能,则不应该修改老的代码,只需要增加新的代码就可以了,这样可以避免对老功能的破坏,新增功能如果有问题,也很容易回退.python语言如何实现这个设计目标呢?可以使用我们之前提到的消息机制来实现:pyt......
  • VTK 实例54:封闭性检测
    1#include"vtkAutoInit.h"2VTK_MODULE_INIT(vtkRenderingOpenGL2);3VTK_MODULE_INIT(vtkInteractionStyle);45#include<vtkSmartPointer.h>6#include<vtkSelectionNode.h>7#include<vtkInformation.h>8#incl......
  • 1254. 统计封闭岛屿的数目
    1254.统计封闭岛屿的数目二维矩阵grid 由0 (土地)和1 (水)组成。岛是由最大的4个方向连通的0 组成的群,封闭岛是一个 完全由1包围(左、上、右、下)的岛。请返回封闭岛屿的数目。示例1:输入:grid=[[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1......
  • 猿辅导推出小猿学练机:10.3寸护眼墨水屏+封闭式系统
    5月30日,沉默近两年的猿辅导在智能硬件领域释放重磅动作,推出旗舰型产品小猿学练机。该产品面向全国中小学生,主打学练一体、以练促学,重新定义学练一体化的数字化产品体系。这一动作代表着,猿辅导正式入局1000亿智能硬件市场。2021年以来,我国新增452家学习硬件相关企业。来自弗若斯特沙......
  • 封闭式开关柜在线测温技术的应用
    安科瑞虞佳豪传统的测温方式有红外点温仪测温、红外热成像仪测温、试温腊片等方式,这几种方式无法实现24小时全天候测量工作,特别是在一些特定场合下,分散监测、环境封闭、有高压电,无法进行有效测温。在电力系统设备在长期的运行中,往往容易产生老化或过热现象,这些现象如果没有及时......
  • luogu P7599 [APIO2021] 雨林跳跃
    题面传送门我成功了,我不再是以前那个我了!我们发现部分分里面有个单点跳到单点,尝试考虑这个部分分。一个点有两个点可以跳,贪心地想,如果我先跳了比较矮的那个,那么再一步能......
  • 化妆品&护肤品多次重复封闭性皮肤斑贴
    随着美容护肤品市场的不断发展,新的化妆品和美容护肤手段层出不穷,化妆品不良反应及化妆品皮肤病的发生几率也逐年增加。为此,国家成立了专门的监督管理机构和检测机构对化妆品......
  • 原始股的封闭期是多久 没有具体的时间规定
    我们知道股票上市之后是有封闭期的,很多人在公司没有上市之前也是会购买大量的原始股的。这些原始股一定公司上市之后也会价值上升,那时候卖出去会获得不菲的收益。不过在此......