没有上司的舞会
题意:一棵树上的每个结点都有一个值,每颗子树的根节点和其他节点不能同时被选,求可以选择的最大值
f[u][0] 表示所有从以u为根的子树中选择,并且不选u点的最大得分
f[u][1] 表示所有从以u为根的子树中选择,并且选u点的最大得分对于节点u的子节点j:
如果选u点,则f[u][1]累加所有不选子节点j的情况
如果不选u点,则f[u][0]累加选择子节点j或不选子节点j中较大的一个
//dfs(root);//从根节点遍历
void dfs(int u)
{
f[u][1]=w[u];
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
dfs(j);
f[u][0]+=max(f[j][0],f[j][1]);
f[u][1]+=f[j][0];
}
}
树的最长路径(树的直径)
题意:找到一条路径,使得路径上的边的权值之和最大
思路:
和
分别表示以u为根节点向下的最长/次长路径长度
经过点u的最长路径长度,必然等于
int d1[N],d2[N];
void dfs(int u,int fa){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
//子节点j的最长路径+父节点u到子节点j的距离
dfs(j,u);
int t=d1[j]+w[i];
if(t>d1[u]){//更新最长路
d2[u]=d1[u];
d1[u]=t;
}else if(t>d2[u]){//更新次长路
d2[u]=t;
}
}
res=max(res,d1[u]+d2[u]);
}
补充:另一种求树的直径的方式 (存在负权边时该方法不成立)
①从任意点开始,找到离他最远的点u
②再从点u开始,找离u最远的点
③此时u离u最远的点距离即为树的直径
struct edge{
int id;
int w;
};
vector<edge> h[N];//h[i]以i为顶点的边
int dis[N];
void dfs(int u,int father,int d){
dis[u]=d;
for(int i=0;i<h[u].size();i++){
edge t=h[u][i];
if(t.id==father) continue;
dfs(t.id,u,d+t.w);
}
}
int main(){
cin>>n;
for(int i=0;i<n-1;i++){
int a,b,c;
cin>>a>>b>>c;
h[a].push_back({b,c});
h[b].push_back({a,c});
}
//从任意点开始,找到离他最远的点u
dfs(1,-1,0);
int u=1;
for(int i=1;i<=n;i++)
if(dis[i]>dis[u])
u=i;
//再从点u开始,找离u最远的点
dfs(u,-1,0);
for(int i=1;i<=n;i++)
if(dis[i]>dis[u])
u=i;
//此时u离u最远的点距离即为树的直径
int res=dis[u];
}
树的中心(换根DP)
题意:找出离其他结点的最远距离最近的一点
思路:
和
分别表示u向下的最长/次长路径长度
表示u的最长向下路径的子节点
表示u向上的最长路径长度
先dfs_down,求出
和
,同时记录
再dfs_up,对于每个点j (父节点u) ,从
、
(或
,根据
确定)选出较长的一条来计算
此时每个点都有
![]()
![]()
,即向下的最长、次长距离,向上的最长距离,根据题意计算即可
int d1[N],d2[N],up[N];
int p[N];
void dfs_down(int u,int fa){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
dfs_down(j,u);
int t=d1[j]+w[i];
if(t>d1[u]){
d2[u]=d1[u];
d1[u]=t;
p[u]=j;//最长向下路径的子节点
}else if(t>d2[u]){
d2[u]=t;
}
}
}
void dfs_up(int u,int fa){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
up[j]=up[u]+w[i];
if(p[u]==j){//j在u的最长向下路径上
up[j]=max(up[j],d2[u]+w[i]);
}else{
up[j]=max(up[j],d1[u]+w[i]);
}
dfs_up(j,u);
}
}
dfs_down(1,-1);
dfs_up(1,-1);
int res=0x3f3f3f3f;
for(int i=1;i<=n;i++){
int d=max(max(d1[i],d2[i]),up[i]);
res=min(res,d);
}
cout<<res;
数字转换
根据给定数和约数之和的关系构建树,然后求树的直径即可
约数之和->质数、约数
int d1[N],d2[N];
void dfs_down(int u,int fa){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
dfs_down(j,u);
int t=d1[j]+w[i];
if(t>d1[u]){
d2[u]=d1[u];
d1[u]=t;
}else if(t>d2[u]){
d2[u]=t;
}
}
res=max(res,d1[u]+d2[u]);
}
int cal(int x){
int tx=x;
map<int,int> ms;
for(int i=2;i<=x/i;i++){
if(x%i==0){
int s=0;
while(x%i==0){
x/=i;
s++;
}
ms[i]+=s;
}
}
if(x>1) ms[x]++;
int sum=1;
for(auto t:ms){
int p=t.first,a=t.second;
int ts=1;
while(a--){
ts*=p;
ts++;
}
sum*=ts;
}
//题目要求约数之和中不包含本身
return sum-tx;
}
int main(){
cin>>n;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++){
int t=cal(i);
//题目要求正整数范围内,将0去除
if(t==0) continue;
if(t<i){
add(t,i,1);add(i,t,1);
}
}
dfs_down(1,-1);
cout<<res;
}
二叉苹果树
题意:一颗二叉树,每个树枝有相应得分,只能保留q个树枝,求最大得分
思路:
表示以i为根节点的树,树枝不超过j个 的最大苹果数
对于节点u的每个子节点j,枚举以u为根的子树所占的树枝个数k,再枚举以j为根的子树所占的树枝个数z,由于u和j之间还有一根树枝需要预留,所以
转移方程:
int f[N][N];
//f[i][j] 以i为根节点的树,树枝不超过j个 的最大苹果数
void dfs(int u,int fa){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
dfs(j,u);
//相当于01背包的一维,需要倒序更新
for(int k=q;k>=0;k--){
for(int z=0;z<k;z++){
f[u][k]=max(f[u][k],f[u][k-z-1]+f[j][z]+w[i]);
}
}
}
}
//dfs(1,-1)
//f[1][q]
战略游戏
题意:在一棵树的n个节点上放置士兵,每条边的两个端点至少有一个士兵,求最少放多少
思路:
表示节点i放/不放的情况下,以i为根节点的子树最少放多少个
对于节点u,①如果u上没有放置,则子节点j上必须放置,
②如果u上放置了,则子节点j上放不放均可,取较小值,
注意:如果u上放置,在f[u][1]中需要加上u本身的一个,即
int f[N][2];
//f[i][0/1] 节点i 放/不放
void dfs(int u,int fa){
//初始化u的状态
f[u][0]=0,f[u][1]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
dfs(j,u);
//如果u没放,则j必须放
//如果u放了,则j放不放都可以
f[u][0]+=f[j][1];
f[u][1]+=min(f[j][0],f[j][1]);
}
}
//dfs(0,-1)
//min(f[0][0],f[0][1])
皇宫看守
题意:在一棵树的n个节点上放置士兵,每个位置有各自的花费,每个节点要么放置,要么其邻点放置,求最小花费(区别于上一题,上一题中要求的是边,这一题要求的是点)
思路:
int f[N][3];
//f[i][0/1/2]
//节点i不放 其父节点放
//节点i不放 其子节点放
//节点i放
void dfs(int u){
//初始化u的状态
f[u][0]=0;
f[u][1]=0x3f3f3f3f;
f[u][2]=w[u];
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
dfs(j);
f[u][0]+=min(f[j][1],f[j][2]);
f[u][2]+=min(f[j][0],min(f[j][1],f[j][2]));
}
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
f[u][1]=min(f[u][1],f[u][0]+f[j][2]-min(f[j][1],f[j][2]));
}
}
//dfs(root);
//cout<<min(f[root][1],f[root][2])
叶子的颜色
(1)每个节点有三种选择:染白色、染黑色和不染色,但可以证明染白/黑色一定包含最优解
(2)状态转移:
(3)根节点的选择,可以证明选择任意一个根节点的结果都相同
#include<bits/stdc++.h>
using namespace std;
#define fast(); ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e4+5,M=N<<1,INF=0x3f3f3f3f;
int n,m;
int c[N];
int h[N],e[M],ne[M],idx;
int f[N][2];//f[i][0/1] 以i为根节点的子树,且i染白/黑色,该子树的染色节点数量
int d[N];
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int dfs(int u,int fa){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
dfs(j,u);
f[u][0]+=min(f[j][0]-1,f[j][1]);
f[u][1]+=min(f[j][1]-1,f[j][0]);
}
return min(f[u][1],f[u][0]);
}
int main(){
memset(h,-1,sizeof h);
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>c[i];
}
for(int i=1;i<m;i++){
int a,b;cin>>a>>b;
d[a]++,d[b]++;
add(a,b);add(b,a);
}
for(int i=1;i<=m;i++){
if(i<=n){//叶节点
f[i][c[i]]=1;
f[i][!c[i]]=INF;
}else{
f[i][0]=f[i][1]=1;
}
}
cout<<dfs(n+1,-1)<<endl;
}
生命之树
题意:求权值之和最大的子树的和
表示以i为根的子树,选i或不选i,得到的最大权值之和
① 由于要保证选择的点联通,当不选i时,最多只能选一个子分支,即
② 当选i时,选择所有分数为正的分支,即
最后枚举所有子树,求的最大值即为答案
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e5+5,M=N<<1,INF=0x3f3f3f3f;
int n;
int h[N],e[M],ne[M],idx;
int s[N];
ll f[N][2];
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void dfs(int u,int fa){
f[u][1]=s[u];
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
dfs(j,u);
f[u][0]=max(f[j][0],f[j][1]);
f[u][1]+=max(0ll,f[j][1]);
}
}
int main(){
memset(h,-1,sizeof h);
memset(f,-0x3f,sizeof f);
cin>>n;
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
add(u,v);add(v,u);
}
dfs(1,-1);
ll res=-0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=n;i++){
res=max(res,max(f[i][0],f[i][1]));
}
cout<<res<<endl;
}