[BZOJ]3257树的难题 树型DP

26 篇文章 0 订阅

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值