题解:P14309 【MX-S8-T2】配对

修正了部分标点符号。

树形 DP,时间复杂度 \(\Theta(n)\)。赛后听出题人讲题写的,感觉自己的实现可能有点复杂,但是还是供予参考一下,自认为比较清晰,列出了详细的状态转移方程。篇幅有点长,如有错误,敬请指出。

UPD:喜提最复杂、码量最大、常数最大、最难写做法,转化成背包好写很多,能省很多码量,但是题解(shi)都写(la)了大家就将就着看(chi)吧。

STEP 1:考虑最简单情况下(\(c\) 中有偶数个 \(1\),没有交换操作)怎么统计答案。发现如果任意两点的匹配存在路径上重边,那这个匹配一定是不优的,因为可以重边两边的点分别内部匹配使答案去掉这条重边。

这样我们得出一个贪心:把树变成有根树(根不重要),从下往上考虑,应该优先匹配子树内所有点。

考虑对每个点计算答案,令 \(u\) 到父亲 \(fa\) 的边权为 \(w_u\),其贡献就是 \(w_u((\sum_{v\in tree(u)}c_v)\bmod 2)\)。为了方便不妨记 \(f_u=(\sum_{v\in tree(u)}c_v)\bmod 2\),整体答案就是 \(\sum_u w_uf_u\)

STEP 2:我们发现做完上述转化后,我们只需要知道 \(u\) 内有多少个节点 \(v\)\(c_v\) 发生了变化,就可以轻松计算 \(u\) 的贡献,与 \(v\) 的实际值无关。这是一个很好的性质,那我们直接上 DP。发现根据题意,先设一维分四种情况:

  1. 子树内没有要移动的点。
  2. 子树内有一个原本的 \(1\) 要变成 \(0\)
  3. 子树内有一个原本的 \(0\) 要变成 \(1\)
  4. 子树内已经有一对 \(0,1\) 互换了位置。

再考虑到奇偶性,我们发现奇数个点需要舍弃一个 \(1\),那我们再用第二维来描述这个情况:

  1. 子树内没有删掉 \(1\)
  2. 子树内删掉了一个原本的 \(1\)

这样我们就可以得到一个方程:\(f_{u,0/1/2/3,0/1}\) 表示 \(u\) 子树内分别在 \(8\) 种状态下的最小费用。我们让 \(\oplus\) 表示按位异或操作,分别讨论一下这 \(8\) 种状态对 \(w_u\) 贡献产生的影响。

考虑不删 \(1\)

  • 对于 \(f_{u,0,0}\),其贡献为 \(w_uf_u\)
  • 对于 \(f_{u,1,0}\),其贡献为 \(w_u(f_u\oplus 1)\)
  • 对于 \(f_{u,2,0}\),其贡献为 \(w_u(f_u\oplus 1)\)
  • 对于 \(f_{u,3,0}\),其贡献为 \(w_uf_u\)

考虑删 \(1\)

  • 对于 \(f_{u,0,1}\),其贡献为 \(w_u(f_u\oplus 1)\)
  • 对于 \(f_{u,1,1}\),其贡献为 \(w_uf_u\)
  • 对于 \(f_{u,2,1}\),其贡献为 \(w_uf_u\)
  • 对于 \(f_{u,3,1}\),其贡献为 \(w_u(f_u\oplus 1)\)

由于树形 DP 是对于每个子树分步转移的一个并列结构,我们不妨让 \(dt_{0/1/2/3,0/1}\) 表示处理完 \(u\) 及其前面处理的子树的最小费用(称这个合并为 \(u\) 子树),当前需要转移子树根为 \(v\)(称为 \(v\) 子树),那么我们就可以利用上述方法转移。

需要注意:

  1. 发现如果 \(w_u\)\(f_u\) 在转移过程中的贡献发生了改变也要相应的加减。
  2. \(u\) 子树和 \(v\) 子树的合并除 \(w_u\) 贡献转移以外是等价的,因此 \(f_{u,3,0}\) 可以由 \(dt_{1,0}+f_{v,2,0}\) 或者 \(dt_{2,0}+f_{v,1,0}\) 转移过来。

这里我们列一下 \(f_{u,X,0}\) 的转移:

\[\begin{aligned} f_{u,0,0}=&dt_{0,0}+f_{v,0,0}\\ f_{u,1,0}=&\min(dt_{1,0}+f_{v,0,0},dt_{0,0}+f_{v,1,0}+w_u((f_u\oplus 1)-f_u))\\ f_{u,2,0}=&\min(dt_{2,0}+f_{v,0,0},dt_{0,0}+f_{v,2,0}+w_u((f_u\oplus 1)-f_u))\\ f_{u,3,0}=&\min(dt_{3,0}+f_{v,0,0},\\ &dt_{1,0}+f_{v,2,0}+w_u(f_u-(f_u\oplus 1)),\\ &dt_{2,0}+f_{v,1,0}+w_u(f_u-(f_u\oplus 1)) \end{aligned} \]

然后可以发现 \(f_{u,X,1}\) 的转移是差不多的,只需要增加一个 \(Y\) 枚举一下删掉的 \(1\) 是不是由 \(u\) 子树贡献的,然后注意删掉 \(1\)\(w_u\) 贡献的相应影响即可。(详解:对于 \(w_u\) 贡献的转移,假设原方程需要从 \(dt_{u,X,j}\) 转移,原来(相对于 \(f_{u,X,0}\) 的情况)的贡献转移是 \(w_u((f_u\oplus A)-(f_u\oplus B))\),那么新的贡献转移就是 \(w_u((f_u\oplus A\oplus 1)-(f_u\oplus B\oplus j))\),直接复制一遍然后在 \(w_u\) 贡献上改即可。)

讲一下初始化的问题,先全局赋值为 \(+\infty\)

  • \[f_{u,0,0}=w_uf_u \]

  • \[\begin{cases}f_{u,1,0}=w_u(f_u\oplus 1),f_{u,1,1}=w_uf_u & c_u=0\\f_{u,0,1}=w_u(f_u\oplus 1),f_{u,2,0}=w_u(f_u\oplus 1) & c_u=1\end{cases} \]

具体意义读者可以手推一下。

最后答案就是 \(\min(f_{root,0,cnt_1\bmod 2},f_{root,3,cnt_1\bmod 2})\)\(cnt_1\) 是原树中 \(c_v=1\)\(v\) 的数量。

感觉代码写的有点繁杂,仅供参考:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+5;
const LL INF=1e16;
int n,head[N],idx,F[N],W[N];
int cnt1;
LL f[N][4][2],dt[4][2];
bool c[N];
struct Edge{int v,next,w;}e[N<<1];
void link(int x,int y,int z){
	e[++idx].v=y;
	e[idx].next=head[x];
	e[idx].w=z;
	head[x]=idx;
}
void dfs0(int u,int fa){
	F[u]=(c[u]==1);
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v,w=e[i].w;
		if(v==fa)continue;
		W[v]=w;
		dfs0(v,u);
		F[u]+=F[v];
	}
	F[u]&=1;
}
void chkmin(LL &a,LL b){a=min(a,b);}
void dfs(int u,int fa){
	f[u][0][0]=W[u]*F[u];
	if(c[u])f[u][2][0]=W[u]*(F[u]^1),
		f[u][0][1]=W[u]*(F[u]^1);
	else f[u][1][0]=W[u]*(F[u]^1),
		f[u][1][1]=W[u]*F[u];
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v,w=e[i].w;
		if(v==fa)continue;
		dfs(v,u);
		for(int j=0;j<4;j++)
			for(int k=0;k<2;k++)
				dt[j][k]=f[u][j][k],
				f[u][j][k]=INF;
		f[u][0][0]=dt[0][0]+f[v][0][0];
		f[u][1][0]=min(dt[1][0]+f[v][0][0],dt[0][0]+W[u]*((F[u]^1)-F[u])+f[v][1][0]);
		f[u][2][0]=min(dt[2][0]+f[v][0][0],dt[0][0]+W[u]*((F[u]^1)-F[u])+f[v][2][0]);
		f[u][3][0]=min(min(dt[3][0]+f[v][0][0],dt[0][0]+f[v][3][0]),
			min(dt[1][0]+W[u]*(F[u]-(F[u]^1))+f[v][2][0],
			dt[2][0]+W[u]*(F[u]-(F[u]^1))+f[v][1][0]));
		
		for(int j=0;j<2;j++){
			chkmin(f[u][0][1],dt[0][j]+W[u]*((F[u]^1)-(F[u]^j))+f[v][0][1-j]);
			chkmin(f[u][1][1],min(dt[1][j]+W[u]*(F[u]-(F[u]^1^j))+f[v][0][1-j],
				dt[0][j]+W[u]*(F[u]-(F[u]^j))+f[v][1][1-j]));
			chkmin(f[u][2][1],min(dt[2][j]+W[u]*(F[u]-(F[u]^1^j))+f[v][0][1-j],
				dt[0][j]+W[u]*(F[u]-(F[u]^j))+f[v][2][1-j]));
			chkmin(f[u][3][1],min(
				min(dt[3][j]+W[u]*((F[u]^1)-(F[u]^j))+f[v][0][1-j],
				dt[0][j]+W[u]*((F[u]^1)-(F[u]^j))+f[v][3][1-j]),
				min(dt[1][j]+W[u]*((F[u]^1)-(F[u]^j^1))+f[v][2][1-j],
				dt[2][j]+W[u]*((F[u]^1)-(F[u]^j^1))+f[v][1][1-j])));
		}
	}
}
int main(){
	//freopen("match.in","r",stdin);
	//freopen("match.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>c[i],cnt1+=(c[i]==1);
		for(int j=0;j<4;j++)
			for(int k=0;k<2;k++)
				f[i][j][k]=INF;
	}
	for(int i=1;i<n;i++){
		int u,v,w;cin>>u>>v>>w;
		link(u,v,w);link(v,u,w);
	}
	dfs0(1,0);
	dfs(1,0);
	cout<<min(f[1][3][cnt1&1],f[1][0][cnt1&1]);
	return 0;
}
posted @ 2025-10-26 09:11  TBSF_0207  阅读(35)  评论(0)    收藏  举报