题目大意
t个询问,每个询问
给一个n个点树,边有权值,每个点有一个范围在0~2的点值。
删去若干条边,花费代价为边权和,使森林中的每一个树都满足
点值为0的点的个数为0或点值为1的点的个数小于2。
求最小花费代价。(t<=5,n<=300000,边权<=10^9)
树形DP
很显然是树形动规。
设f[i,j,k]表示点i为根的子树中,有j个点的点值为0,有k个点的点值为1时的最小花费。
j的范围0~1,因为j>0后的值都等价,无需在乎它到底是多少,记作1就好了。
同理k的范围0~2。
转移就很简单了,所以就不细讲了。
不过会爆栈,所以要打人工栈(真烦)。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=300000+10;const ll man=300000000000000;
int a[maxn],k[maxn],next[maxn*2],num,g[maxn*2],b[maxn],h[maxn];
ll f[maxn][2][3],c[maxn*2],s[maxn][2][3],ans;
void read(int &n){
char ch=getchar();
while((ch<'0')||(ch>'9'))ch=getchar();int q=0,w=1;
while(ch>='0' && ch<='9')q=q*10+ch-'0',ch=getchar();n=q*w;
}
void add(int x,int y,int z){
next[++num]=k[x];
k[x]=num;g[num]=y;c[num]=z;
}
void dfs(int x){
int i;memset(s[0],63,sizeof(s[0]));num=0;
if (a[x]==0) s[0][1][0]=0;else
if (a[x]==1) s[0][0][1]=0;else
s[0][0][0]=0;i=k[x];
while (i>0){
if (b[g[i]]>b[x]) {
num++;int p=g[i];
memset(s[num],63,sizeof(s[num]));
for (int j=0;j<=1;j++)
for (int l=0;l<=2;l++) if (s[num-1][j][l]<man){
for (int j1=0;j1<=1;j1++)
for (int l1=0;l1<=2;l1++) if (f[p][j1][l1]<man){
if ((j1==0)||(l1<=1)) s[num][j][l]=min(s[num][j][l],s[num-1][j][l]+c[i]+f[p][j1][l1]);
int j2=j+j1,l2=l+l1;
if (j2>1) j2=1;if (l2>2) l2=2;
s[num][j2][l2]=min(s[num][j2][l2],s[num-1][j][l]+f[p][j1][l1]);
}
}
}
i=next[i];
}
memset(f[x],63,sizeof(f[x]));
for (int j=0;j<=1;j++)
for (int l=0;l<=2;l++) f[x][j][l]=s[num][j][l];
}
int main(){
int t;read(t);
for (t=t;t>0;t--){
int n;read(n);num=0;
memset(k,0,sizeof(k));memset(b,0,sizeof(b));
for (int i=1;i<=n;i++) read(a[i]);
for (int i=1;i<n;i++){
int x,y,z;read(x);read(y);read(z);
add(x,y,z);add(y,x,z);
}int j=1,x,i;
b[1]=h[1]=num=1;
while (j<n){
x=h[j];i=k[x];
while (i>0){
if (b[g[i]]==0) {
b[g[i]]=++num;
h[num]=g[i];
}i=next[i];
}j++;
}ans=man;
for (int q=n;q>0;q--)x=h[q],dfs(x);
for (int l=0;l<=2;l++) ans=min(ans,f[1][0][l]);
ans=min(ans,min(f[1][1][0],f[1][1][1]));
printf("%lld\n",ans);
}
}