[CQOI2017] 小Q的棋盘(贪心 / 树形dp)

problem

luogu-P3698

solution1-贪心

显然我们想尽可能地少走回头路,即一直往下走。

所以我们可以都会有个初步地猜测是走最长链。

但很快就会想到万一这是一条单链,链中的点都是二度点,走得越深回头浪费的步数也越多。

然后就可能直接把这种想法抛却了。

以上都是脑中不到两分钟的粗略思路,很容易误判。

实际上仔细想想,建议画几个树,就会发现最优解总可以调整成走长链。

当开始走长链的时候,就一定不会走回头路了

考虑一条链,当走到底的时候发现有多余的步数,然后又发现链中某点有多个儿子,我们现在才返回去走一条新链的话,这个浪费的步数就太多了。

所以我们应当是预知有多余的步数,然后再走到那个点的时候,就先走新链,然后再倒回来继续走这条链。

对于新链而言一样按照上面的预知流程执行。

可以感觉地到这是个递归的过程。

但这并不是代码实现,这只是来佐证贪心猜测的正确性的。

这也是网上题解说的 走完最长链上的点后,每两步能到达一个新的点 ,这不是代表着一定是先走完最长链再回头,还是最优解可以将步数和点数按照这种方式对应上。

时间复杂度 O ( n ) O(n) O(n),所以说出题人完全可以将 n n n 开大点,卡掉 d p dp dp 的做法。

为你的良心点赞👍

code1

#include <bits/stdc++.h>
using namespace std;
#define maxn 105
vector < int > G[maxn];
int n, m, ans;

void dfs( int u, int fa, int dep ) {
	ans = max( ans, dep );
	for( int v : G[u] ) 
		if( v == fa ) continue;
		else dfs( v, u, dep + 1 );
}

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1, u, v;i < n;i ++ ) {
		scanf( "%d %d", &u, &v );
		G[u].push_back( v );
		G[v].push_back( u );
	}
	dfs( 0, 0, 1 );
	if( m <= ans ) printf( "%d\n", m + 1 );
	else printf( "%d\n", min( n, ans + (m - ans + 1) / 2 ) );
	return 0;
}

solution2-树形dp

这种样子,长得就像个树形 d p dp dp

f ( i , j ) : f(i,j): f(i,j): 在以 i i i 为根的子树内,从 i i i 开始向下在子树内走了 j j j 步最多经过的节点数。

然后可能就直接从上往下用 f a fa fa f f f 更新 u u u f f f ,此时有新加点;

然后搜索 u u u 子树,完了回来又从下往上更新,此时不会有新加点。

实际上这种做法的正确性必须保证对于一棵树 dfs \text{dfs} dfs 先搜的就是最长链,相当于是跑的贪心。

实在不理解的可以自己写个数据生成器,不用多大 8 8 8 以内都能给你排出错误来。

其实问题与贪心思考的一样,就是是否回到这个点来。我们将这个变成第三维。

f ( i , j , 0 / 1 ) : 0 f(i,j,0/1):0 f(i,j,0/1):0 不回到 u u u 点, 1 1 1 回到 u u u 点。

则对于 u u u ,枚举儿子 v v v,以及自己一共在子树内走的步数,和 v v v 在子树内走的步数:
{ f ( u , i , 0 ) = max ⁡ { f ( u , i − j , 1 ) + f ( v , j − 1 , 0 ) , f ( u , i − j , 0 ) + f ( v , j − 2 , 1 ) } f ( u , i , 1 ) = max ⁡ { f ( u , i − j , 1 ) + f ( v , j − 2 , 1 ) } \begin{cases} f(u,i,0)=\max\Big\{f(u,i-j,1) + f(v,j-1,0),f(u,i-j,0) + f(v,j-2,1)\Big\}\\ f(u,i,1)=\max\Big\{f(u,i-j,1) + f(v,j-2,1)\Big\} \end{cases} f(u,i,0)=max{f(u,ij,1)+f(v,j1,0),f(u,ij,0)+f(v,j2,1)}f(u,i,1)=max{f(u,ij,1)+f(v,j2,1)}
回来的话 u − v u-v uv 这条边就要走两次,所以是 − 2 -2 2,否则 − 1 -1 1

时间复杂度 O ( n 3 ) O(n^3) O(n3)

哪哪儿都比不上贪心好吧

code2

#include <bits/stdc++.h>
using namespace std;
#define maxn 105
vector < int > G[maxn];
int n, m;
int f[maxn][maxn][2];

void dfs( int u, int fa ) {
	f[u][0][1] = f[u][0][0] = 1;
	for( int v : G[u] )
		if( v ^ fa ) {
			dfs( v, u );
			for( int i = m;i;i -- )
				for( int j = 1;j <= i;j ++ ) {
					if( j > 0 ) f[u][i][0] = max( f[u][i][0], f[u][i - j][1] + f[v][j - 1][0] );
					if( j > 1 ) f[u][i][0] = max( f[u][i][0], f[u][i - j][0] + f[v][j - 2][1] );
					if( j > 1 ) f[u][i][1] = max( f[u][i][1], f[u][i - j][1] + f[v][j - 2][1] );
				}
		}
}

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1, u, v;i < n;i ++ ) {
		scanf( "%d %d", &u, &v );
		G[u].push_back( v );
		G[v].push_back( u );
	}
	dfs( 0, 0 );
	int ans = 0;
	for( int i = 0;i <= m;i ++ ) ans = max( ans, max( f[0][i][1], f[0][i][0] ) );
	printf( "%d\n", ans );
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值