树形dp杂题

树形dp杂题

模板/思维题

P1122 最大子树和

求的是树上最大的一个连通分量的大小

那么我们设置 f[i] 表示以 i 为根的子树中(强制选取 i )的最大联通分量的大小

如果 f[v]>0 转移即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define eb emplace_back
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , f[N] , maxx = -inf , a[N];

vector<int> e[N];
inl void add ( int u , int v ) { e[u].eb(v); }

void dfs ( int u , int fa )
{
	f[u] = a[u];
	for ( auto v : e[u] )
		if ( v ^ fa ) 
		{
			dfs ( v , u );
			if ( f[v] > 0 ) f[u] += f[v];
		}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
	for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	dfs ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) maxx = max ( maxx , f[i] );
	cout << maxx << endl;
	return 0;
}

P2016 战略游戏

设置 f[i][0/1] 表示这个节点放/不放士兵的最小个数

那么如果这个点不放士兵(0) 那么显然子节点必须都放 因为需要满足看到所有(而不是点) 则 f[u][0]+=f[v][1]

同理 如果这个点放士兵(1) 那么子节点放不放都行 取最小值 则 f[u][0]+=min(f[v][0],f[v][1])

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f;

inline int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , a[N] , f[N][2];
//设f[u][0]表示当前处理到u节点 然后这个节点不选
//同理 f[u][1]表示当前处理到u节点 这个节点选择

int head[N] , cnt;
struct edge { int to , nxt; } e[N];
void add ( int u , int v ) { e[++cnt] = { v , head[u] }; head[u] = cnt; }

void dfs ( int u , int fa )
{
	f[u][1] = 1;
	for ( int i = head[u] ; i ; i = e[i].nxt )
	{
		int v = e[i].to;
		if ( v == fa ) continue;
		dfs ( v , u );
		f[u][0] += f[v][1];
		f[u][1] += min ( f[v][1] , f[v][0] );
	}
}



signed main ()
{
	n = read();
	for ( int i = 1 , u , v , k ; i < n ; i ++ ) 
	{
		u = read() , k = read();
		for ( int i = 1 ; i <= k ; i ++ )
		{
			v = read();
			add ( u , v );
			add ( v , u );
		}
	}
	dfs ( 1 , n );
	printf ( "%d" ,	min ( f[1][0] , f[1][1] ) );
	return 0;
}

P4084 [USACO17DEC] Barn Painting G

经典套路:设置 f[i][1/2/3] 表示 i 节点染成 1/2/3 颜色的方案数

那么 f[u][1]=f[v][2]+f[v][3] 对于 2/3 颜色同理

#include<bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define int long long 
#define print(x) cerr<<#x<<'='<<x<<endl
const int N = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f3f3f3f3f;
#define getchar() cin.get()

int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , k , f[N][4] , a[N];

vector<int> e[N];
inl void add ( int u , int v ) { e[u].eb(v); }

void dfs ( int u , int ff )
{
	for ( int i = 1 ; i <= 3 ; i ++ ) f[u][i] = 1;
	if ( a[u] )
	{
		for ( int i = 1 ; i <= 3 ; i ++ )
			if ( i ^ a[u] ) f[u][i] = 0;
	}
	for ( auto v : e[u] )
		if ( v ^ ff )
		{
			dfs ( v , u );
			( f[u][1] *= ( f[v][2] + f[v][3] ) ) %= mod;
			( f[u][2] *= ( f[v][1] + f[v][3] ) ) %= mod;
			( f[u][3] *= ( f[v][1] + f[v][2] ) ) %= mod;
		}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , k = read();
	for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	for ( int i = 1 , u , col ; i <= k ; i ++ ) u = read() , col = read() , a[u] = col;
	dfs ( 1 , 0 );
	cout << ( f[1][1] + f[1][2] + f[1][3] ) % mod << endl;
	return 0;
}

P3047 [USACO12FEB] Nearby Cows G

我们记录 g[i][j] 表示以 i 为根节点的子树中 向下 j 层能覆盖到的节点 设置 f[i][j] 表示以 i 为根节点的子树中 向上和向下一共能覆盖多少的节点

那么很显然我们可以一次 dfs 预处理 g[i][j] 也就是 g[i][j]=g[v][j1](1jk)

同时 f 数组也可以通过 dfs 求出 即为: f[v][i]+=f[u][i1]g[v][i2] 画图容斥一下即可

#include<bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
#define inl inline
#define eb
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
const int N = 1e5 + 5;
const int K = 25;
#define getchar() cin.get()

int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , k , f[N][K] , g[N][K] , a[N];
//g[i][j]表示在以i为根的子树中 向下延伸j长度的权值和
//f[i][j]表示在以i为根的子树中 周围j长度的权值和

vector<int> e[N];
inl void add ( int u , int v ) { e[u].eb(v); }

void dfs ( int u , int ff )
{
	for ( int i = 0 ; i <= k ; i ++ ) g[u][i] = a[u];
	for ( auto v : e[u] )
		if ( v ^ ff ) 
		{
			dfs ( v , u );
			for ( int i = 1 ; i <= k ; i ++ ) g[u][i] += g[v][i-1];
		}
}

void dfss ( int u , int ff )
{
	for ( auto v : e[u] )
		if ( v ^ ff )
		{
			f[v][1] += f[u][0];
			for ( int i = 2 ; i <= k ; i ++ ) f[v][i] += f[u][i-1] - g[v][i-2];
			dfss ( v , u );
		}


}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , k = read();
	for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
	dfs ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) for ( int j = 1 ; j <= k ; j ++ ) f[i][j] = g[i][j];
	
	memcpy ( f , g , sizeof g );
	dfss ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) cout << f[i][k] << endl;
	return 0;
}

P2585 [ZJOI2006] 三色二叉树

思路很顺畅 先递归建树再 dp 设置 f[i][j] 表示 i 节点染成 j 颜色的绿色节点最大个数 g[i][j] 是最小个数即可

#include<bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
const int N = 5e5 + 5;
const int K = 25;
#define getchar() cin.get()

int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , tot , rt , ch[N][2] , f[N][3] , g[N][3];

string s;

void build ( int &u )
{
	u = ++tot;
	if ( s[u-1] == '1' ) build ( ch[u][0] );
	else if ( s[u-1] == '2' ) build ( ch[u][0] ) , build ( ch[u][1] );
}

void dfs ( int u )
{
	int l = ch[u][0] , r = ch[u][1];
	if ( l ) dfs(l);
	if ( r ) dfs(r);
	if ( !l && !r ) f[u][0] = g[u][0] = 1;
	f[u][0] = max ( f[l][1] + f[r][2] , f[l][2] + f[r][1] ) + 1; 
	f[u][1] = max ( f[l][0] + f[r][2] , f[l][2] + f[r][0] ); 
	f[u][2] = max ( f[l][0] + f[r][1] , f[l][1] + f[r][0] ); 
	g[u][0] = min ( g[l][1] + g[r][2] , g[l][2] + g[r][1] ) + 1; 
	g[u][1] = min ( g[l][0] + g[r][2] , g[l][2] + g[r][0] );
	g[u][2] = min ( g[l][0] + g[r][1] , g[l][1] + g[r][0] ); 
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	cin >> s;
	build(rt);
	dfs ( rt );
	cout << max ( max ( f[rt][0] , f[rt][1] ) , f[rt][2] ) << ' ' << min ( min ( g[rt][0] , g[rt][1] ) , g[rt][2] ) << endl;
	return 0;
}

[P1131 [ZJOI2007] 时态同步

我们可以维护最长链 固定最长链上的价值不动 然后提升其余链的价值 这样一定不劣

树形 dp 统计即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define int long long
const int N = 1e6 + 5;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
 #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , s , ans , dis[N];

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

void dfs ( int u , int fa )
{
	for ( auto p : e[u] )
	{
		int v = p.fi , w = p.se;
		if ( v ^ fa )
		{
			dfs ( v , u );
			dis[u] = max ( dis[u] , dis[v] + w );
		}
	}
	for ( auto p : e[u] )
	{
		int v = p.fi , w = p.se;
		if ( v ^ fa ) ans += dis[u] - ( dis[v] + w );
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , s = read();
	for ( int i = 1 , u , v , w ; i < n ; i ++ ) u = read() , v = read() , w = read() , add ( u , v , w ) , add ( v , u , w );
	dfs ( s , 0 );
	cout << ans << endl;
	return 0;
}							

P4438 [HNOI/AHOI2018] 道路

解释:乡村为叶节点 城市为中间节点 整颗树是二叉树

设置 f[u][i][j] 表示 u 节点到根节点选取 i 个公路和 j 个铁路的最小代价

dp 柿子: f[dfn[u]][i][j]=min(f[dfn[son[u][0]]][i+1][j]+f[dfn[son[u][1]]][i][j],f[dfn[son[u][0]]][i][j]+f[dfn[son[u][1]]][i][j+1])

相当于是要么这个点要么翻修铁路 要么翻修公路 更新对应的权值

对于叶子节点 我们预处理出所有的 f[u][i][j] 即可

发现 40000×40×40 空间开不下 那么我们可以在 dp 过程中记录 dfn 表示下属状态 因为我们一次修改的是一条链 所以不会出现状态覆盖的情况 这样空间可以很小

#include<bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define print(x) cerr<<#x<<'='<<x<<endl
#define int long long 
const int N = 4e5 + 5;
#define getchar() cin.get()
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , dfn[N] , f[405][45][45] , a[N] , b[N] , c[N] , son[N][2];

void dfs ( int u , int timer , int limi , int limj )
{
	dfn[u] = timer;
	if ( u >= n )
	{
		for ( int i = 0 ; i <= limi ; i ++ )
			for ( int j = 0 ; j <= limj ; j ++ )
				f[dfn[u]][i][j] = c[u] * ( a[u] + i ) * ( b[u] + j );
		return;
	}
	dfs ( son[u][0] , timer + 1 , limi + 1 , limj ) , dfs ( son[u][1] , timer + 2 , limi , limj + 1 );
	for ( int i = 0 ; i <= limi ; i ++ )
		for ( int j = 0 ; j <= limj ; j ++ )
			f[dfn[u]][i][j] = min ( f[dfn[son[u][0]]][i+1][j] + f[dfn[son[u][1]]][i][j] , f[dfn[son[u][0]]][i][j] + f[dfn[son[u][1]]][i][j+1] );
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i < n ; i ++ )
	{
		int s = read() , t = read();
		s = ( s < 0 ) ? ( -s + n - 1 ) : s;
		t = ( t < 0 ) ? ( -t + n - 1 ) : t;
	 	son[i][0] = s , son[i][1] = t;
	}
	for ( int i = 1 ; i <= n ; i ++ ) a[i+n-1] = read() , b[i+n-1] = read() , c[i+n-1] = read();
	dfs ( 1 , 1 , 0 , 0 );
	cout << f[1][0][0] << endl;
	return 0;
}

Garland

我们需要达到的目的是将一整棵树分成点权相同的三个部分

那么首先如果 sum 点权不是 3 的倍数 那么显然无解

否则我们进行树上的贪心 f[u] 表示 u 为根的子树内的点权之和

那么如果 f[u]==sum/3 那么直接将这个子树剪掉 用 vector 记录即可

#include<bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
const int N = 1e6 + 5;
#define getchar() cin.get()
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int a[N] , sum , n , f[N] , rt;

vector<int> e[N] , vec;
inl void add ( int u , int v ) { e[u].eb(v); }

void dfs ( int u , int ff )
{
	f[u] = a[u];
	for ( auto v : e[u] )
		if ( v ^ ff )
		{
			dfs ( v , u );
			f[u] += f[v];
		}
	if ( f[u] == sum ) vec.eb(u) , f[u] = 0;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 , ff ; i <= n ; i ++ )
	{
		ff = read();
		if ( ff ) add ( ff , i ) , add ( i , ff );
		else rt = i;
		sum += ( a[i] = read() );
	}
	if ( sum % 3 ) return cout << -1 << endl , 0;
	sum /= 3;
	dfs ( rt , 0 );
	if ( vec.size() <= 2 ) { cout << -1 << endl; exit(0); }
	else cout << vec[0] << ' ' << vec[1] << endl;
	return 0;
}

树上背包

P2014 [CTSC1997] 选课

设置 f[i][j] 表示以 i 为根节点的子树中 选择 j 个课程的价值

根据套路 我们在 dfs 后枚举给 v 子树分配多少个课程 进行转移

注意倒序枚举 m 因为会使用到当前节点上一次的 j 较小的状态

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
const int N = 1e3 + 5;
const int inf = 0x3f3f3f3f;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , f[N][N] , m;

int head[N] , cnt;
struct node { int to , nxt; } e[N];
inl void add ( int u , int v ) { e[++cnt] = { v , head[u] }; head[u] = cnt; }

void dfs ( int u )
{
	for ( int i = head[u] ; i ; i = e[i].nxt )
	{
		int v = e[i].to;
		dfs(v);
		for ( int j = m ; j ; j -- )
			for ( int k = 1 ; k < j ; k ++ )//不能枚举到j 因为我们状态定义的就是u这个根节点必须选 因为只有选了这个节点才能选子节点
				f[u][j] = max ( f[u][j] , f[v][j-k] + f[u][k] );
	}
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) 
	{
		int u = read();
		f[i][1] = read();
		add ( u , i );
	}
	m ++ , dfs(0);
	cout << f[0][m] << endl;
	return 0;
}

P2015 二叉苹果树

和上一道题的转移状态只有一点不同: f[u][j]=max(f[u][j],f[v][k1]+f[u][jk]+w) 这里的 k1 是因为我们想要取得 w 这条边 那么 v 子树内就需要少一条边

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define eb emplace_back
const int N = 100 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , f[N][N];

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

void dfs ( int u , int fa )
{
	for ( auto [v,w] : e[u] )
		if ( v ^ fa )
		{
			dfs ( v , u );
			for ( int j = m ; j ; j -- )
				for ( int k = 1 ; k <= j ; k ++ )
					f[u][j] = max ( f[u][j] , f[v][k-1] + f[u][j-k] + w );
		}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 ; i < n ; i ++ )
	{
		int u = read() , v = read() , w = read();
		add ( u , v , w ) , add ( v , u , w );
	}
	dfs ( 1 , 0 );
	cout << f[1][m] << endl;
	return 0;
}

P1273 有线电视网

设置 f[i][j] 表示树上以 i 为根的子树内 选择 j 个用户提供服务的最大收益 然后记录 sz 同树形背包基本转移即可

注意 最后统计答案的时候从大到小枚举用户个数 j 遇到 f[i][j]0 的情况就输出 j 即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define eb emplace_back
const int N = 3000 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , f[N][N] , a[N] , sz[N];

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

void dfs ( int u , int fa )
{
	if ( u >= n - m + 1 ) return f[u][1] = a[u] , sz[u] = 1 , void();
	for ( auto [v,w] : e[u] )
		if ( v ^ fa )
		{
			dfs ( v , u );
			sz[u] += sz[v];
			for ( int j = sz[u] ; j ; j -- )
				for ( int k = 1 ; k <= min ( j , sz[v] ) ; k ++ )
					f[u][j] = max ( f[u][j] , f[v][k] + f[u][j-k] - w );
		}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 ; i <= n - m ; i ++ )
	{
		int k = read();
		for ( int j = 1 ; j <= k ; j ++ ) 
		{
			int A = read() , C = read();
			add ( i , A , C ) , add ( A , i , C );
		}
	}
	for ( int i = n - m + 1 ; i <= n ; i ++ ) a[i] = read();
	memset ( f , -inf , sizeof f );
	for ( int i = 1 ; i <= n ; i ++ ) f[i][0] = 0;
	dfs ( 1 , 0 );
	for ( int i = m ; i >= 0 ; i -- ) if ( f[1][i] >= 0 ) return cout << i << endl , 0; 
	return 0;
}

P1270 “访问”美术馆

设置 f[i][j] 表示以 i 为根节点的子树内 用 j 时间可以偷的最多画

显然有转移方程 f[u][j]=max(f[u][j],f[u][jk]+f[v][kw])

w 的原因是如果要加入 v 节点的贡献 需要首先通过一条边走到 v 节点

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define eb emplace_back
const int N = 6000 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , f[N][N] , a[N] , tot = 1 , tim;

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

void build ( int u , int fa )
{
	int w = read() , v = read();
	add ( fa , u , w << 1 ) , add ( u , fa , w << 1 );//进去+出来
	if ( v ) return a[u] = v , void();
	build ( ++tot , u ) , build ( ++tot , u );
}

void dfs ( int u , int fa )
{
	if ( a[u] ) { for ( int i = 1 ; i <= a[u] ; i ++ ) f[u][i*5] = i; return; }
	for ( auto [v,w] : e[u] )
		if ( v ^ fa )
		{
			dfs ( v , u );
			for ( int j = tim ; j ; j -- )
				for ( int k = w ; k <= j ; k ++ )
					f[u][j] = max ( f[u][j] , f[u][j-k] + f[v][k-w] );
		}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	tim = read();
	build ( ++tot , 1 );
	dfs(1,0);
	cout << f[1][tim-1] << endl;
	return 0;
}

P3360 偷天换日

和上道题一样 只是叶节点处理的时候换成了 01 背包

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define eb emplace_back
const int N = 600 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , f[N][N] , tot = 1 , tim , k[N];

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

struct node { int wei , val; } a[N][N];

void build ( int u , int fa )
{
	int w = read(); k[u] = read();
	add ( fa , u , 2 * w ) , add ( u , fa , 2 * w );
	if ( k[u] )
	{
		for ( int i = 1 ; i <= k[u] ; i ++ ) a[u][i].val = read() , a[u][i].wei = read();
		return;
	}
	build ( ++ tot , u );
	build ( ++ tot , u );
}

void dfs ( int u , int fa )
{
	if ( k[u] )
	{
		for ( int i = 1 ; i <= k[u] ; i ++ )
			for ( int j = tim ; j >= a[u][i].wei ; j -- )
				f[u][j] = max ( f[u][j] , f[u][j-a[u][i].wei] + a[u][i].val );
		return;
	}
	for ( auto [v,w] : e[u] )
		if ( v ^ fa )
		{
			dfs ( v , u );
			for ( int j = tim ; j ; j -- )
				for ( int k = w ; k <= j ; k ++ )
					f[u][j] = max ( f[u][j] , f[v][k-w] + f[u][j-k] );
		}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	tim = read();
	build ( ++ tot , 1 );
	dfs ( 1 , 0 );
	cout << f[1][tim-1] << endl;
	return 0;
}

换根dp

P3478 [POI2008] STA-Station

换根 dp 入门题目

我们考虑先将以 1 为根的情况先求出来

一个很朴素的想法就是换 n 次根来进行 dfs 但这样是 TLE 考虑优化

发现我们如果知道了树上父亲节点的答案 就可以推出子节点的答案

例如:

这里以 5 为根:

img

换成以 4 为根:

img

可以发现 4 节点和它的子树内节点的深度都 1 其他节点深度都 +1

抽象到代码中:上面的 5u 节点 4v 节点 我们已经求出了 5 的答案 f[u] 那么 f[v]=f[u]sz[v]+(nsz[v])

所以我们第一遍先从下到上求出深度 第二遍从上到下推即可

记得 long long

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define pii pair<int,double>
#define fi first
#define se second
#define mkp make_pair
#define fi first
#define se second
#define int long long
const int N = 1e6 + 5;
const double inf = 0x3f3f3f3f;
// char buf[1<<24] , *p1 , *p2;
// #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , ans , id , f[N] , dep[N] , sz[N];

vector<int> e[N];
inl void add ( int u , int v ) { e[u].eb(v); }

void dfs ( int u , int fa )
{
	dep[u] = dep[fa] + 1 , sz[u] = 1;
	for ( auto v : e[u] )
		if ( v ^ fa )
		{
			dfs ( v , u );
			sz[u] += sz[v];
		}
}

void DFS ( int u , int fa )
{
	for ( auto v : e[u] )
		if ( v ^ fa )
		{
			f[v] = f[u] - sz[v] + n - sz[v];
			DFS ( v , u );
		}
}


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read();
	for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	dfs ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) f[1] += dep[i];
	DFS ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) if ( ans < f[i] ) ans = f[i] , id = i;
	cout << id << endl;
	return 0;
}

Choosing Capital for Treeland

可以先将根节点需要的翻转次数先算出来 那么对于 (u,v) 这条边 如果 (u,v) 是正向的 那么将 v 作为根的时候就需要再将这条边反转一遍 反之亦然

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first 
#define se second
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
 #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , minn = inf , f[N];

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

void dfs ( int u , int ff )
{
	for ( auto p : e[u] )
	{
		int v = p.fi , w = p.se;
		if ( v ^ ff )
		{
			dfs ( v , u );
			f[u] += f[v] + w;
		}
	}
}

void dfss ( int u , int ff )
{
	for ( auto p : e[u] )
	{
		int v = p.fi , w = p.se;
		if ( v ^ ff )
		{
			f[v] = f[u] + ( w ? -1 : 1 );
			dfss ( v , u );
		}
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read();
	for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , add ( u , v , 0 ) , add ( v , u , 1 );
	dfs ( 1 , 0 );
	dfss ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) minn = min ( minn , f[i] );
	cout << minn << endl;
	for ( int i = 1 ; i <= n ; i ++ ) if ( f[i] == minn ) cout << i << ' ';
	return 0;
}

P2986 [USACO10MAR] Great Cow Gathering G

一定注意 第二次 dfs 的时候不能是 sz[u] 而是 sz[1] 原因是我们从父亲节点作为根的答案来推子节点的时候 父亲节点的 sz 并不是根节点的 sz

#include<bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define int long long 
#define print(x) cerr<<#x<<'='<<x<<endl
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
#define getchar() cin.get()

int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , r , mod , a[N] , sz[N] , szz[N] , f[N] , dep[N] , minn = inf;

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

void dfs ( int u , int ff )
{
	sz[u] = a[u];
	for ( auto p : e[u] )
	{
		int v = p.fi , w = p.se;
		if ( v ^ ff ) dep[v] = dep[u] + w , dfs ( v , u ) , sz[u] += sz[v];
	}
}

void dfss ( int u , int ff )
{
	for ( auto p : e[u] )
	{
		int v = p.fi , w = p.se;
		if ( v ^ ff ) f[v] = f[u] + ( sz[1] - sz[v] * 2 ) * w , dfss ( v , u );
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
	for ( int i = 1 , u , v , w ; i < n ; i ++ ) u = read() , v = read() , w = read() , add ( u , v , w ) , add ( v , u , w );
	dfs ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) f[1] += a[i] * dep[i];
	dfss ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) minn = min ( minn , f[i] );
	cout << minn << endl;		
	return 0;
}

Tree Painting

同上()

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define eb emplace_back
#define endl '\n'
#define int long long
const int N = 2e5 + 5;

inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}


int f[N] , g[N] , maxx , sz[N] , n;

vector<int> e[N];
inl void add ( int u , int v ) { e[u].eb(v); }

void dfs ( int u , int ff )
{
	sz[u] = 1;
	for ( auto v : e[u] )
		if ( v ^ ff )
			dfs ( v , u ) , f[u] += f[v] , sz[u] += sz[v];
	f[u] += sz[u];
}

void dfss ( int u , int ff )
{
	for ( auto v : e[u] )
		if ( v ^ ff )
		{
			g[v] = g[u] - sz[v] + n - sz[v];
			dfss ( v , u );
		}
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	dfs ( 1 , 0 ) , g[1] = f[1];
	dfss ( 1 , 0 );
	for ( int i = 1 ; i <= n ; i ++ ) maxx = max ( g[i] , maxx );
	cout << maxx << endl;
	return 0;
}
posted @   Echo_Long  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示