题解: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\) 要变成 \(0\)。
- 子树内有一个原本的 \(0\) 要变成 \(1\)。
- 子树内已经有一对 \(0,1\) 互换了位置。
再考虑到奇偶性,我们发现奇数个点需要舍弃一个 \(1\),那我们再用第二维来描述这个情况:
- 子树内没有删掉 \(1\)。
- 子树内删掉了一个原本的 \(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\) 子树),那么我们就可以利用上述方法转移。
需要注意:
- 发现如果 \(w_u\) 对 \(f_u\) 在转移过程中的贡献发生了改变也要相应的加减。
- \(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}\) 的转移:
然后可以发现 \(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;
}

浙公网安备 33010602011771号