[BZOJ3257]树的难题
[BZOJ3257]树的难题
给出一个无根树。树有N个点,边有权值。每个点都有颜色,是黑色、白色、灰色这三种颜色之一,称为一棵三色树。
可爱的Alice觉得,一个三色树为均衡的,当且仅当,树中不含有黑色结点或者含有至多一个白色节点。然而,给出的三色树可能并不满足这个性质。所以,Alice打算删去若干条边使得形成的森林中每棵树都是均衡的,花费的代价等于删去的边的权值之和。请你计算需要花费的代价最小是多少。多组数据。n<=3e5
感觉挺显然的
状态设\(dp[i][0/1][0/1/2]\)表示与i连通的联通快中有0/1个黑点,有0/1/2个白点,其中吗,大于1个黑点视作1处理,大于2个白点视作2个处理,灰点无所谓,不影响答案所以不计入状态
为了方便,记x1=0/1表示第二维,x2=0/1/2表示第三维
本来我按照u的颜色乱七八糟写了一大堆还挂了,看了一下大佬的处理办法:
分两种情况考虑:把当前u割出去/留着
留着的情况很好考虑:枚举v之前的x1,x2和v的x1,x2,直接转移
讲个细节的东西,对于第二维的两个x1,我们希望
0+1=1+0=1和1+1=1
所以两个x1或起来就行。
对于x2,就没什么好办法,写个东西>2返回2就行
保留的转移方程:
\[cmin(dp[u][x1|vx1][T(x2+vx2)],dp[u][x1][x2]+dp[v][vx1][vx2])
\]
然后割掉的话需要满足的条件是,v自己是一个合法的子树
那么
\[cmin(dp[u][x1][x2],dp[u][x1][x2]+dp[v][vx1][vx2]+w)
\]
还是老样子,对于每次枚举的v,用个新数组来转移,然后覆盖到dp[u]上
#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=x;i<=y;i++)
using namespace std;
typedef long long ll;
const int maxn=300010;
const ll inf=1LL<<62;
struct Edge{
int v,nex,dis;
}edge[maxn<<1];
int cnt,head[maxn],col[maxn],n;
ll dp[maxn][5][5],f[5][5];
void addEdge(int u,int v,int d){
edge[++cnt]=(Edge){v,head[u],d};head[u]=cnt;
}
/*
dp[x][0/1][0/1/2]表示与x相连的连通块中
有0或多个黑,0个,1个或多个白 的答案
*/
inline int XT(int x){
return x>2?2:x;
}
inline void cmin(ll &x,ll y){
if (y<x) x=y;
}
void dfs(int u,int fa){
dp[u][col[u]==0][col[u]==1]=0;
for (int i=head[u];i;i=edge[i].nex){
int v=edge[i].v,w=edge[i].dis;
if (v==fa) continue;
dfs(v,u);
rep(x1,0,1) rep(x2,0,2) f[x1][x2]=inf;
rep(x1,0,1) rep(x2,0,2) if (dp[u][x1][x2]<inf)
rep(vx1,0,1) rep(vx2,0,2) if (dp[v][vx1][vx2]<inf){
cmin(f[x1|vx1][XT(x2+vx2)],dp[u][x1][x2]+dp[v][vx1][vx2]);
if ((vx1==0) || vx2<2) cmin(f[x1][x2],dp[u][x1][x2]+dp[v][vx1][vx2]+w);
}
memcpy(dp[u],f,sizeof(dp[u]));
}
}
int read(){
int x=0;char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) x=x*10+ch-48,ch=getchar();
return x;
}
int main(){
int T=read();
while (T--){
rep(i,1,maxn-1) rep(x1,0,1) rep(x2,0,2) dp[i][x1][x2]=inf;
memset(head,0,sizeof(head));
memset(edge,0,sizeof(edge));
memset(col,0,sizeof(col));
cnt=0;
n=read();
rep(i,1,n) col[i]=read();
rep(i,1,n-1){
int x=read(),y=read(),d=read();
addEdge(x,y,d);addEdge(y,x,d);
}
dfs(1,0);
ll ans=inf;
rep(x2,0,2) ans=min(ans,dp[1][0][x2]);
rep(x2,0,1) ans=min(ans,dp[1][1][x2]);
printf("%lld\n",ans);
}
//getchar();
return 0;
}
反思和要修的锅:
1、为啥保留的时候不用注意dp[u][x1][x2]与dp[v][vx1][vx2]并起来时是否合法?
2、割掉的时候那个转移的式子感觉显然不会调用啊,\(dp[u][x1][x2]+dp[v][vx1][vx2]+w\)感觉始终>dp[u][x1][x2],不知道为啥会转移?