2025/2/15课堂记录
目录
- 数字转换
- 皇宫看守
这是一道树的直径题。
首先,树的直径定义是:树上两个结点之间的最短(加权)路中最长的一条路径(和二分答案没关)
但由于贪心思想,这个路径一定起点终点是两片叶子结点
如图,这棵树的直径就是5,即节点3和节点4之间的最短路径
那么,求树的直径有啥方法?
方法一:找每个节点的最长链与次长链之和的最大值
这是代码
// 树的直径 dp实现;
// https://blog.csdn.net/qq_42211531/article/details/86579115
// dp[u][0]: 结点u的最长儿子链
// dp[u][1]: 结点u的次长儿子链
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const MAX = 100005;
int head[MAX], dp[MAX][2];
int n, s, cnt, ans;
struct EDGE
{
int v, w, next;
}e[MAX];
void Add(int u, int v, int w)
{
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
// 利用孩子的最长链去更新父亲的最长链和次长链
void DFS(int u, int fa)
{
//dp[u][0]:最长子链; dp[u][1]:次长子链
dp[u][0] = dp[u][1] = 0;
for(int i = head[u]; i ; i = e[i].next)
{
int v = e[i].v;
int w = e[i].w;
if(v != fa)
{
DFS(v, u);
if(dp[u][0] < dp[v][0] + w) // 父亲u的最长 < 孩子v的最长 + (u,v)边长
{
dp[u][1]= dp[u][0];
dp[u][0] = dp[v][0] + w;
}
else
if(dp[u][1] < dp[v][0] + w)
dp[u][1] = dp[v][0] + w;
}
}
//枚举经过每个节点的长链,是否最大?
ans = max(ans, dp[u][1] + dp[u][0]);
}
int main()
{
memset(head, 0, sizeof(head));
scanf("%d", &n);
for(int i = 1; i <= n - 1; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
Add(u, v, w);
Add(v, u, w);
}
DFS(1, -1); //假设1作为无根树的树根;
printf("%d\n",ans);
}
从下往上推的话
首先是3,4的最长链,次长链都没有,=0,所以ans还是0
其次是2,先遍历3,最长=0+4,然后来,4,次长=最长=4,最长=0+5,ans=4+5=9
然后是5,最长次长都是0,ans不变
最后是1,最长=5+1,次长=0+2,总和=8<9,ans不变还是9
所以最长就是9,是3->2->4这条
可见,树的直径不一定经过根节点!
方法二:先找距离根节点最远的点,再找距离这个点最远的点(2次dp)
这是代码
//https://blog.csdn.net/Rainfoo/article/details/105290837
//图论-树-最长链(树的直径)
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<stack>
using namespace std;
#define ll long long
const int maxn=2e5+5;
int d[maxn],head[maxn],f_num,ans,tot;
struct E{
int to,next,w;
}edge[maxn];
void add(int u,int v,int z){
edge[tot].to=v;
edge[tot].w=z;
edge[tot].next=head[u];
head[u]=tot++;
}
void dfs(int x,int fa){
if(ans<d[x]){
ans=d[x];
f_num=x;
}
for(int i=head[x];i!=-1;i=edge[i].next){
int v=edge[i].to;
if(v==fa) continue;
d[v]=d[x]+edge[i].w;
dfs(v,x);
}
}
int main(){
memset(head,-1,sizeof(head));
int n,m;cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;cin>>u>>v;
//cin>>w;
add(u,v,w);add(v,u,w);
}
dfs(1,0);
ans=0;
d[f_num]=0;
dfs(f_num,0);
cout<<ans<<endl;
}
从1开始找,最远的点是4,
再从4开始找,最远的节点是3
注意,这里dfs函数中的fa参数指的并不是真正的父节点,只是从哪里来的,即上一个节点
然后这是这道题
只不过所有边的权值都是1罢了
然后n起到一个什么作用呢?
这个n既不是根节点,也不是起点或终点
它只起到一个限定作用,限定所有变换中的数必须<=n
数字转换
#include<iostream>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=0x3f3f3f;
const double eps=1e-5;
const int maxn=5e4+10;
/*题意:若是一个数x的所有约数(不包括他自己)之和sum比他自己小,
那么x可以转化成sum,sum也可以成 x。例如 4可以变为 3,1可以变为7
限制所有数字变换在不跨越 n的正整数范围内举行转化,求不停举行数字变换且无重复数字的最多变换步数*/
int sum[maxn];//预处理每个数的约数之和
int f1[maxn],f2[maxn];//f1:以i为根的树中,i到叶子节点的最长距离,f2 :....次长距离
//直径就是 max(f1[i]+f2[i]) 就是树中所有的两点最短距离中的最大值
//
int n;
void getsum()//预处理每个数的约数之和
{
for(int i=1;i<=n;i++)
{
for(int j=2;j<=n/i;j++)
{
sum[i*j]+=i; //i是i*j的约数
}
}
}
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d",&n);
getsum();
for(int i=n;i>=1;i--)
{
if(sum[i]>=i)
continue;
//把sum[i]看成i的父亲,因为每个i的sum[i]都是唯一的 而能变成i的数不唯一
if(f1[i]+1>f1[sum[i]])
{
f2[sum[i]]=f1[sum[i]];
f1[sum[i]]=f1[i]+1;
}
else
if(f1[i]+1>f2[sum[i]])
f2[sum[i]]=f1[i]+1;
}
int ans=-1;
for(int i=1;i<=n;i++)
ans=max(ans,f1[i]+f2[i]);
printf("%d\n",ans);
return 0;
}
这是一道树形dp题
简单说下思路就行:一个点必须要被看守,那么有三种情况:
在这个点安排守卫,父亲安排守卫,孩子安排守卫
1.在这个点安排守卫,就需要付自己的钱和孩子的钱
优点:没啥顾虑,这个点一定有人看守
缺点:费钱
2.父亲安排守卫就,只需要付孩子的钱就行
优点:省钱
缺点:条件苛刻,必须要父亲自己看守才能使用
3.让孩子安排守卫,只需要付孩子的钱就行
优点:还是省钱
缺点:如果所有孩子都不愿意自己看守(即所有孩子自己看守的花费>让孩子的孩子看守孩子的花费)
这时候必须要强制把一个孩子拉出来干活,即fake
这是代码
// 皇宫看守 loj 10157
// https://www.cnblogs.com/Forever-666/p/11234958.html
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=2200;
struct E{int x,y,next;}mm[MAXN<<1];
int w[MAXN],h[MAXN],len,f[MAXN][3],n;
void ins(int x,int y)
{
++len;
mm[len].x=x;
mm[len].y=y;
mm[len].next=h[x];
h[x]=len;
}
void dfs(int x,int fa)
{
int sum1=0,sum2=0,flag=0,fake=0x7fffffff;
for(int k=h[x];k;k=mm[k].next)
{
int y=mm[k].y;
if(y==fa)
continue;
dfs(y,x);
sum1+=min(f[y][0],min(f[y][1],f[y][2]));
if(f[y][0]<=f[y][2])
{
sum2+=f[y][0];
flag=1;
}
else
{
sum2+=f[y][2];
fake=min(fake,f[y][0]-f[y][2]); //防止所有的孩子都不选,选一个最小的
}
}
f[x][0]=sum1+w[x]; //f[i][0]: 自己站
f[x][1]=sum2; //f[i][1]:被父亲照顾
f[x][2]=sum2; //f[i][2]:被孩子照顾
if(!flag)
f[x][2]+=fake;
}
int main()
{
scanf("%d",&n);
memset(h,0,sizeof h);
len=0;
for(int i=1,x,t,y;i<=n;i++)
{
scanf("%d",&x);
scanf("%d%d",&w[x],&t);
for(int j=1;j<=t;j++)
{
scanf("%d",&y);
ins(x,y);
ins(y,x);
}
if(t==0)
{
f[x][1]=0;
f[x][0]=f[x][2]=w[x];
}
}
dfs(1,0);
printf("%d\n",min(f[1][0],f[1][2]));
for(int i=1;i<=n;i++)cout<<f[i][0]<<" "<<f[i][1]<<" "<<f[i][2]<<"\n";
return 0;
}
本文来自博客园,作者:永韶,转载请注明原文链接:https://www.cnblogs.com/yongshao/p/18717425
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)