[联合省选2022]最大权独立集问题

最大权独立集问题

题解

相当有趣的一道dp题。
我考场上全花时间去搞前两题了,T3只rush了一个暴力,亏大分。

首先我们可以考虑将原来的边交换的条件转化一下。
相当于我们要给每一个点规定一条不经过重复点的有向路径,使得所有点的路径加起来可以覆盖满整棵树。
显然,我们很容易通过这得到一种 d p dp dp的思路。
可以发现,从子树节点的父亲往子树内覆盖的路径只会有一条,从子树内向父亲传递的点也只会有一个。
所以我们 d p dp dp时不妨就记录这个路径回到哪一个点,子树内向上传递又会传递哪一个点。
显然,在一个点上我们需要枚举的就是选择边的顺序,是先父边,先左儿子,还是先右儿子,通过这从底向上转移。
但这样的话我们的 d p dp dp状态总数是会达到 n 3 n^3 n3的,貌似不太能过的样子。
但不要着急,显然,传递到的点与向上传递的点不会在一棵子树内,该状态只会出现在这两个点的 l c a lca lca处。
所以,我们的状态数实际上是 n 2 n^2 n2的。
但我们上面的 d p dp dp状态是可以继续被优化的,因为最后被传递到的位置我们真正关心的只有它的深度,我们把这个深度记到状态里面即可。

好的,我们现在 d p u , x , d dp_{u,x,d} dpu,x,d表示我们现在对于点 u u u的子树内,我们将会把点 x x x传到它的父亲去,并且父亲传下来的点往下传长度为 d d d的路径。
通过考虑边交换的执行顺序,容易得到 d p dp dp转移式:
d p u , u , d 1 = d p l s o n , x , d 1 − 1 + d p r s o n , y , d 2 + ( d 2 + 2 ) v a l x + v a l y d p u , u , d 1 = d p r s o n , x , d 1 − 1 + d p l s o n , y , d 2 + ( d 2 + 2 ) v a l x + v a l y d p u , x , d 1 = d p l s o n , x , d 2 + d p r s o n , y , d 1 − 1 + ( d 2 + 1 ) v a l u + v a l y + v a l x d p u , x , d 1 = d p r s o n , x , d 2 + d p l s o n , y , d 1 − 1 + ( d 2 + 1 ) v a l u + v a l y + v a l x d p u , x , 0 = d p l s o n , y , d 1 + d p r s o n , x , d 2 + ( d 1 + 1 ) v a l u + ( d 2 + 2 ) v a l y + v a l x d p u , x , 0 = d p r s o n , y , d 1 + d p l s o n , x , d 2 + ( d 1 + 1 ) v a l u + ( d 2 + 2 ) v a l y + v a l x dp_{u,u,d_1}=dp_{lson,x,d_1-1}+dp_{rson,y,d_2}+(d_2+2)val_x+val_y\\ dp_{u,u,d_1}=dp_{rson,x,d_1-1}+dp_{lson,y,d_2}+(d_2+2)val_x+val_y\\ dp_{u,x,d_1}=dp_{lson,x,d_2}+dp_{rson,y,d_1-1}+(d_2+1)val_u+val_y+val_x\\ dp_{u,x,d_1}=dp_{rson,x,d_2}+dp_{lson,y,d_1-1}+(d_2+1)val_u+val_y+val_x\\ dp_{u,x,0}=dp_{lson,y,d_1}+dp_{rson,x,d_2}+(d_1+1)val_u+(d_2+2)val_y+val_x\\ dp_{u,x,0}=dp_{rson,y,d_1}+dp_{lson,x,d_2}+(d_1+1)val_u+(d_2+2)val_y+val_x dpu,u,d1=dplson,x,d11+dprson,y,d2+(d2+2)valx+valydpu,u,d1=dprson,x,d11+dplson,y,d2+(d2+2)valx+valydpu,x,d1=dplson,x,d2+dprson,y,d11+(d2+1)valu+valy+valxdpu,x,d1=dprson,x,d2+dplson,y,d11+(d2+1)valu+valy+valxdpu,x,0=dplson,y,d1+dprson,x,d2+(d1+1)valu+(d2+2)valy+valxdpu,x,0=dprson,y,d1+dplson,x,d2+(d1+1)valu+(d2+2)valy+valx总共 6 6 6个冗杂的方程,因为涉及到 l s o n lson lson r s o n rson rson的相对顺序,其实只有 3 3 3个本质不同的转移。
现在,如果我们直接按照这个 d p dp dp转移式去转移的话,是 O ( n 4 ) O\left(n^4\right) O(n4)的,不太能过的样子。
但这明显是可以优化的。
比如第一个式子,我们发现,我们的 y y y只会贡献到 d 2 d_2 d2上,对其它值根本没有影响,所以我们可以对于每个 d 2 d_2 d2找到它选择的最优的 y y y,这个 d 2 d_2 d2之后再第一个转移中只会与这个 y y y匹配。
同样,再这之后,我们的 d 2 d_2 d2也只会被贡献到 x x x上,对于其他值根本没有影响,,所以我们再对于每个 x x x找到这个 d 2 d_2 d2
最后,用同样的方式,把 x x x贡献到 d 1 d_1 d1上,每个 d 1 d_1 d1又会对应到唯一的 d p u , u , d 1 dp_{u,u,d_1} dpu,u,d1,这样就完成我们的转移了。
后面的转移方程式都可以用类似的方法优化,这里就不多阐释了。

容易发现我们上面的转移复杂度的分析是套用树形 d p dp dp的分析方法的,每个点在每个 l c a lca lca处转移时的深度都是它另一子树的深度。
总时间复杂度显然是 O ( n 2 ) O\left(n^2\right) O(n2)的。

源码

然而有点冗杂。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define MAXN 5005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
const LL INF=0x3f3f3f3f3f3f3f3f;
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,val[MAXN],fp[MAXN],mxd[MAXN],ch[MAXN][2],deg[MAXN];
int sta[MAXN][MAXN],stak[MAXN];
LL f[MAXN],g[MAXN],h[MAXN],dp[MAXN][MAXN],tmp[MAXN][MAXN];
LL F[MAXN],Gf[MAXN],Gg[MAXN],ans;
void sakura(int rt){
	sta[rt][++stak[rt]]=rt;int ls=ch[rt][0],rs=ch[rt][1];
	if(!deg[rt]){dp[rt][0]=mxd[rt]=0;return ;}
	if(ls)sakura(ls),h[rt]=max(h[rt],h[ls]+1);
	if(rs)sakura(rs),h[rt]=max(h[rt],h[rs]+1);
	for(int j=0;j<=n;j++)dp[rt][j]=INF;
	if(deg[rt]==1){
		for(int i=1;i<=stak[ls];i++){
			int x=sta[ls][i];mxd[rt]=max(mxd[rt],mxd[x]+1);
			for(int j=0;j<=mxd[x];j++)
				tmp[x][j]=dp[x][j],dp[x][j]=INF;
			for(int j=0;j<=mxd[x];j++)
				dp[rt][j+1]=min(dp[rt][j+1],tmp[x][j]+val[x]),
				dp[x][0]=min(dp[x][0],tmp[x][j]+1ll*(j+1)*val[rt]+val[x]);
			sta[rt][++stak[rt]]=x;mxd[x]=0;
		}
		return ;
	}
	for(int i=0;i<=h[ls];i++)f[i]=Gf[i]=INF;
	for(int i=0;i<=h[rs];i++)g[i]=Gg[i]=INF;
	for(int i=1;i<=stak[ls];i++){
		int x=sta[ls][i];sta[rt][++stak[rt]]=x;
		for(int j=0;j<=mxd[x];j++)
			f[j]=min(f[j],val[x]+(tmp[x][j]=dp[x][j])),dp[x][j]=INF;
	}
	for(int i=1;i<=stak[rs];i++){
		int x=sta[rs][i];sta[rt][++stak[rt]]=x;
		for(int j=0;j<=mxd[x];j++)
			g[j]=min(g[j],val[x]+(tmp[x][j]=dp[x][j])),dp[x][j]=INF;
	}
	for(int i=1;i<=stak[ls];i++){
		int x=sta[ls][i];LL minn=F[x]=INF,mn=INF;
		for(int j=0;j<=h[rs];j++)
			minn=min(minn,g[j]+1ll*val[x]*(j+2));
		for(int j=0;j<=mxd[x];j++)
			dp[rt][j+1]=min(dp[rt][j+1],minn+tmp[x][j]),
			mn=min(mn,tmp[x][j]+1ll*(j+1)*val[rt]);
		minn=INF;
		for(int j=0;j<=mxd[x];j++)
			minn=min(minn,tmp[x][j]+1ll*val[rt]*(j+1));
		for(int j=0;j<=h[rs];j++)
			dp[x][j+1]=min(dp[x][j+1],minn+g[j]+val[x]),
			Gg[j]=min(Gg[j],1ll*(j+2)*val[x]+mn);
	}
	for(int i=1;i<=stak[rs];i++){
		int x=sta[rs][i];LL minn=INF,mn=INF;
		for(int j=0;j<=h[ls];j++)
			minn=min(minn,f[j]+1ll*val[x]*(j+2));
		for(int j=0;j<=mxd[x];j++)
			dp[rt][j+1]=min(dp[rt][j+1],minn+tmp[x][j]),
			mn=min(mn,tmp[x][j]+1ll*(j+1)*val[rt]);
		minn=INF;
		for(int j=0;j<=mxd[x];j++)
			minn=min(minn,tmp[x][j]+1ll*val[rt]*(j+1));
		for(int j=0;j<=h[ls];j++)
			dp[x][j+1]=min(dp[x][j+1],minn+f[j]+val[x]),
			Gf[j]=min(Gf[j],1ll*(j+2)*val[x]+mn);
	}
	for(int i=1;i<=stak[ls];i++){
		int x=sta[ls][i];LL minn=INF;
		for(int j=0;j<=mxd[x];j++)minn=min(minn,tmp[x][j]+Gf[j]),tmp[x][j]=INF;
		dp[x][0]=min(dp[x][0],minn+val[x]);mxd[x]=h[rs]+1;
	}
	for(int i=1;i<=stak[rs];i++){
		int x=sta[rs][i];LL minn=INF;
		for(int j=0;j<=mxd[x];j++)minn=min(minn,tmp[x][j]+Gg[j]),tmp[x][j]=INF;
		dp[x][0]=min(dp[x][0],minn+val[x]);mxd[x]=h[ls]+1;
	}
	mxd[rt]=h[rt];
}
int main(){
	read(n);ans=INF;
	for(int i=1;i<=n;i++)read(val[i]);
	for(int i=2,x;i<=n;i++)
		read(x),ch[x][deg[x]++]=i;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=n;j++)
			tmp[i][j]=dp[i][j]=INF;
	sakura(1);
	for(int i=1;i<=n;i++)ans=min(ans,dp[i][0]);
	printf("%lld\n",ans);
	return 0;
}

谢谢!!!

### 匈牙利算法实现最大权匹配解决方案 #### 背景介绍 匈牙利算法主要用于解决二分图中的最大匹配问题。然而,在处理最大权匹配时,通常会结合KM(Kuhn-Munkres)算法来完成更复杂的任务分配问题[^3]。 对于最大权匹配而言,单纯依靠匈牙利算法并不足以解决问题,因为匈牙利算法主要关注的是如何找到尽可能多的不相交边集合作为匹配路径,而忽略了每条边上可能存在的权重差异。因此,为了适应带权情况下的最优解需求,需要引入额外机制调整节点之间的潜在价值评估体系,从而引导算法向更高总收益的方向发展。 #### KM算法简介及其与匈牙利算法的关系 KM算法是在匈牙利算法基础上扩展而来的一种专门针对加权完全二分图的最佳完美匹配寻找策略。其核心思想在于通过构建辅助结构——即所谓的“可行顶标”,使得每次尝试增加当前已知最佳匹配的过程中都能保持全局效益最大化原则不变。具体来说: - 定义左部和右部分别对应的任务方与执行者; - 给定任意一对关联点(i,j),设定初始期望值h[i]+s[j]=w[i][j],其中w表示实际成本矩阵元素; - 当前状态下任未饱和结点作为起点发起增广操作直至无法继续为止; - 更新所有涉及变动位置的新旧差额至相应标签上,重复上述过程直到获得满意结果。 ```cpp #include <iostream> #include <vector> using namespace std; const int INF = 1e9; int n; // number of nodes on each side vector<vector<int>> w(n, vector<int>(n)); // weight matrix vector<int> lx(n), ly(n); // labels for X and Y sets vector<bool> S(n), T(n); vector<int> match_y(n); bool dfs(int i){ S[i] = true; for (int j = 0; j < n; ++j) { if (!T[j]) { int slack = lx[i] + ly[j] - w[i][j]; if (slack == 0) { T[j] = true; if (match_y[j] == -1 || dfs(match_y[j])) { match_y[j] = i; return true; } } else { /* do nothing */ } } } return false; } void update_labels(){ int delta = INF; for (int i = 0; i < n; ++i) if (S[i]) for (int j = 0; j < n; ++j) if (!T[j]) delta = min(delta, lx[i] + ly[j] - w[i][j]); for (int i = 0; i < n; ++i) if (S[i]) lx[i] -= delta; for (int j = 0; j < n; ++j) if (T[j]) ly[j] += delta; } // Main function to find maximum weighted matching using KM algorithm. double km_algorithm() { fill(lx.begin(), lx.end(), 0); fill(ly.begin(), ly.end(), 0); fill(match_y.begin(), match_y.end(), -1); while(true){ fill(S.begin(), S.end(), false); fill(T.begin(), T.end(), false); bool found_augmenting_path = false; for (int i = 0; !found_augmenting_path && i < n ;++i) if (match_y[i]==-1) found_augmenting_path |= dfs(i); if(found_augmenting_path) continue; update_labels(); } double result=0.; for(size_t j=0;j<n;++j) if(match_y[j]!=-1) result+=w[match_y[j]][j]; return result; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值