dp[v][0/1][0/1/2]表示dp到v点,与v联通的一块没有黑点/有黑点,没有白点/有一个白点/有很多白点所需最小代价。考虑v与其儿子的边删还是不删转移就可以了。
这个好复杂,其实令dp[i][0/1/2]表示以i为根的子树中,满足题目要求且i所在的联通块是无黑色点/无白色点/有一个白色点的边权和最小值。
转移应当十分显然。
令u为i的其中一个儿子,len为连接u,i的边权+dp[u]中的最小值。
则dp[i][0]=min{dp[i][0]+dp[u][0],dp[i][0]+len}
dp[i][1]=min{dp[i][1]+dp[u][1],dp[i][1]+len}
dp[i][2]=min{dp[i][2]+dp[u][1],dp[i][1]+dp[u][2],dp[i][2]+len}
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=300010;
const ll inf=1000000000000000;
struct edge
{
int t,w;
edge *next;
}*con[maxn];
int n,col[maxn];
ll f[2][maxn][2][3];
int z0[2][3][2]={{{0,0}},{{1,0},{0,1},{1,1}}},z1[3][6][2]={{{0,0}},{{1,0},{0,1}},{{0,2},{2,0},{2,1},{1,2},{1,1},{2,2}}};
int t0[2]={0,2},t1[3]={0,1,5};
int read()
{
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x;
}
void ins(int x,int y,int w)
{
edge *p=new edge;
p->t=y;
p->w=w;
p->next=con[x];
con[x]=p;
}
int dfs(int v,int fa)
{
for(int j=0;j<=1;j++)
for(int k=0;k<=2;k++)
f[0][v][j][k]=f[1][v][j][k]=inf;
if(col[v]==0) f[0][v][1][0]=0;
if(col[v]==1) f[0][v][0][1]=0;
if(col[v]==2) f[0][v][0][0]=0;
int x=0;
for(edge *p=con[v];p!=NULL;p=p->next)
if(p->t!=fa)
{
int y=dfs(p->t,v);
x^=1;
for(int c0=0;c0<=1;c0++)
for(int c1=0;c1<=2-c0;c1++)
{
f[x][v][c0][c1]=inf;
for(int d0=0;d0<=1;d0++)
for(int d1=0;d1<=2-d0;d1++)
f[x][v][c0][c1]=min(f[x][v][c0][c1],f[x^1][v][c0][c1]+p->w+f[y][p->t][d0][d1]);
for(int d0=0;d0<=t0[c0];d0++)
for(int d1=0;d1<=t1[c1];d1++)
{
f[x][v][c0][c1]=min(f[x][v][c0][c1],f[x^1][v][z0[c0][d0][0]][z1[c1][d1][0]]+f[y][p->t][z0[c0][d0][1]][z1[c1][d1][1]]);
}
}
}
return x;
}
int main()
{
int ca=read();
while(ca--)
{
for(int i=1;i<=n;i++)
con[i]=NULL;
n=read();
for(int i=1;i<=n;i++)
col[i]=read();
for(int i=1;i<=n-1;i++)
{
int x=read(),y=read(),w=read();
ins(x,y,w);
ins(y,x,w);
}
int y=dfs(1,0);
printf("%lld\n",min(f[y][1][0][0],min(f[y][1][0][1],min(f[y][1][0][2],min(f[y][1][1][1],f[y][1][1][0])))));
}
return 0;
}