题目
大意:
给出n个节点的树,有k个节点需染成特殊颜色,总共需要染成m个不同颜色(包括特殊颜色),求相邻两个点为同一颜色的边的权值和最小值
具体思想
真的没有看题解,但是不知道为什么zxy是1ms出解
我觉得这道题如果转化成这样就有点像四色原理了,所以一棵树可以仅用两种颜色就可遍历完。
所以我们可以这样定义dp方程了,不是总共有k个点为特殊颜色吗,则
dp[x][j][0/1]表示以x为子树的这棵树有j个点为特殊颜色,且它的根(x节点)是/否 被染成特殊颜色
接着,我们可以简单地用dp背包列出状态转移方程(好像所有的背包方程长得都差不多??)
w表示权值,k是枚举它的儿子需要k个点(包括儿子自己)是特殊颜色
v是x的儿子节点
但是有一个问题,就是我们发现如果m=2则这棵树就只有两种颜色了,而除特殊颜色外的另一种颜色可能会连续多个点都染成同一个颜色,所以:
由于只有两种颜色,若父亲儿子都不涂成特殊颜色,则它们一定为相同颜色,所以要加上权值
那如果m!=2呢,其实就不存在这种特殊情况了,父亲和儿子完全可以选两个不同的颜色:
那么,就是一个普通的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;
}