[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],不知道为啥会转移?

猜你喜欢

转载自www.cnblogs.com/ugly-CYW-lyr-ddd/p/11802245.html