贪吃的九头龙

7 篇文章 0 订阅
6 篇文章 0 订阅

题目

大意:

给出n个节点的树,有k个节点需染成特殊颜色,总共需要染成m个不同颜色(包括特殊颜色),求相邻两个点为同一颜色的边的权值和最小值

具体思想

真的没有看题解,但是不知道为什么zxy是1ms出解

我觉得这道题如果转化成这样就有点像四色原理了,所以一棵树可以仅用两种颜色就可遍历完。

所以我们可以这样定义dp方程了,不是总共有k个点为特殊颜色吗,则

dp[x][j][0/1]表示以x为子树的这棵树有j个点为特殊颜色,且它的根(x节点)是/否 被染成特殊颜色

接着,我们可以简单地用dp背包列出状态转移方程(好像所有的背包方程长得都差不多??)

dp[x][j][1] = \sum_{son x } \sum_{j} \sum_{k}=min(dp[x][j][1] , min( dp[v][k][0]+dp[x][j-k][1] , dp[v][k][1] + dp[x][j-k][1] + w ) )

w表示权值,k是枚举它的儿子需要k个点(包括儿子自己)是特殊颜色

v是x的儿子节点

但是有一个问题,就是我们发现如果m=2则这棵树就只有两种颜色了,而除特殊颜色外的另一种颜色可能会连续多个点都染成同一个颜色,所以:

dp[x][j][0] = \sum_{sonx}\sum_{j}\sum_{k}min( dp[x][j][0] , min( dp[v][k][0]+dp[x][j-k][0]+w , dp[v][k][1] , dp[x][j-k][0] ) )

由于只有两种颜色,若父亲儿子都不涂成特殊颜色,则它们一定为相同颜色,所以要加上权值

 

那如果m!=2呢,其实就不存在这种特殊情况了,父亲和儿子完全可以选两个不同的颜色:

dp[x][j][0] = \sum_{son x }\sum_{j}\sum_{k}min(dp[x][j][0] , min( dp[x][j-k][0] + dp[v][k][0] , dp[x][j-k][0] + dp[v][k][1] ) )

那么,就是一个普通的DP了。

还要注意2个问题:

如果点不够,也就是n-k<m-1,也就是除了特殊的节点染成特殊颜色外,其它节点总数还不足m-1,则无解 那么就输出-1

边界条件,其实有两个(还蛮好找到的):

dp[x][0][0] = 0;

dp[x][1][1] = 0

也都很好理解吧!?

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
const int MAXN = 303;
int f[MAXN][MAXN][2] , sz[MAXN];
int c[MAXN][MAXN][2];
int n , m , t;
int x , y , z;
struct node{
    int v , w;
    node(){}
    node( int V , int W ){
        v = V;
        w = W;
    }
};
vector< node > G[MAXN];
void dfs( int x , int fa ){
    sz[x] = 1;
    for( int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i].v , w = G[x][i].w;
        if( v == fa ) continue;
        dfs( v , x );
        sz[x] += sz[v];
        memcpy( c , f , sizeof( c ) );//注意我们算的时候不能直接算,要先存储一下,要拿上一层的
        memset( f[x] , 0x3f , sizeof( f[x] ) );
        for( int j = min( t , sz[x] ) ; j >= 0 ; j -- ){//简单优化一下
            for( int k = 0 ; k <= min( sz[v] , j ) ; k ++ ){
                f[x][j][1] = min( f[x][j][1] , min( f[v][k][1] + c[x][j-k][1] + w , f[v][k][0] + c[x][j-k][1] ) );
                if( m == 2 )
                    f[x][j][0] = min( f[x][j][0] , min( f[v][k][0] + c[x][j-k][0] + w , f[v][k][1] + c[x][j-k][0] ) );
                else
                    f[x][j][0] = min( f[x][j][0] , min( f[v][k][0] + c[x][j-k][0] , f[v][k][1] + c[x][j-k][0] ) );
            }
        }
    }
}
int main(){
    scanf( "%d%d%d" , &n , &m , &t );
    for( int i = 1 ; i < n ; i ++ ){
        scanf( "%d%d%d" , &x , &y , &z );
        G[x].push_back( node( y , z ) );
        G[y].push_back( node( x , z ) );
    }
    if( n - t < m - 1 ){
        printf( "-1" );
        return 0;
    }//无解情况
    memset( f , 0x3f , sizeof( f ) );
    for( int i = 1 ; i <= n ; i ++ ){
        f[i][1][1] = f[i][0][0] = 0;
    }//边界
    dfs( 1 , 0 );
    printf( "%d" , f[1][t][1] );
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值