树的难题

题目大意

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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值