HDU6769 2020多校第二场 二分树规

树的直径本质上是最大值,要最小化它则二分
Check核心:使得对每个点,在树的直径不超过mid的前提下,
经过它的最长的路径尽可能最短
记f[u][k]为u的子树中选择k条边的最长路径的最小值
为什么这样定义?因为在dp过程中不停地用父子两个点的最长链拼成直径并且判断
是否大于mid,这样只要最后f[1][k]被更新了就说明存在合法解

初始化:对于点u,一开始对所有的k都是无穷大(在tmp中)
F[u][0]=0,可以理解为第一个点进来之前u点还不存在属于自己的链
难点:判断是否满足直径不大于mid与自身转化
很多树规都存在自身转化现象
防止自身转化冲突:开一个新数组tmp保存,最后再赋值回原数组
接着枚举点u通向儿子v的边选a还是选b,分为两种情况更新(见代码)
优化(不做T):一开始我是先单独求一个点下面边的数量,这样t了因为每个点都是两重循环,复杂度每个点k*in,in为度 而如果一边遍历儿子一边增加size数组,复杂度会略微下降,事实上究竟能优化到什么程度实在是玄学,但是出题人卡了这个优化。。
`//#include"bits/stdc++.h"
//#include<unordered_map>
//#include<unordered_set>
#include
#include
#include
#include
#include <string.h>
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define ll long long
#define sc(x) scanf("%d",&x)
#define pr(x) printf("%d",x)
#define scl(x) scanf("%lld",&x)
#define prl(x) printf("%lld",x)
#define prn(x) printf("%d\n",x)

#define fin for(int i=1;i<=n;i++)
#define fim for(int j=1;j<=m;j++)
#define fri freopen(“t.txt”,“r”,stdin);
#define fro freopen(“a.out”,“w”,stdout);
#define seto struct o{int x;int y;};bool comp(o a,o b){return a.x>b.x;}
#define prio priority_queueq;
#define prio1 priority_queue<int,vector,greater >q;
#define pb push_back
#define MAP map<int,int>mp;map<int,int>::iterator it;
#define mapgo for(it=mp.begin();it!=mp.end();it++)
#define VEC vector
#define sf sizeof
//#define sz size()
#define emp empty()
#define mem memset
using namespace std;
ll n,K,ans,l,r,mid;
ll inf;
struct o
{
ll x,a,b;
};
vectora[200005];
ll f[25][200005];
ll sz[200005];
void dfsz(int x,int fa)
{
for(int i=0;i<a[x].size();i++)
{
int j=a[x][i].x;
if(j==fa)continue;
dfsz(j,x);
sz[x]+=sz[j];
}
//统计每个点有多少个子树,最后要-- ,原来我是这么写的但是事实证明T了
sz[x]++;
}

void dfs(int x,int fa)
{
for(int i=0;i<=K;i++)f[i][x]=inf2;
f[0][x]=0;
ll tmp[25];//存储中间变化的f
sz[x]=0;int up=0;
for(int i=0;i<a[x].size();i++)
{
int v=a[x][i].x;
if(v==fa)continue;
dfs(v,x);
up=min(K,sz[v]+sz[x]+1);
for(int j=0;j<=K;j++)
tmp[j]=inf
2;
for(int j=0;j<=sz[x];j++)
{
for(int k=0;k<=sz[v]&&j+k<=K;k++)
{
if(j+k+1<=K&&f[j][x]+a[x][i].a+f[k][v]<=mid)//选择一条a中的边,判断直径是否满足要求
tmp[j+k+1]=min(tmp[j+k+1],max(f[j][x],a[x][i].a+f[k][v]));
//u中已经遍历节点得出的最小值和当前点v产生的取大
if(j+k<=K&&f[j][x]+a[x][i].b+f[k][v]<=mid)
tmp[j+k]=min(tmp[j+k],max(f[j][x],a[x][i].b+f[k][v]));
//选择b中的边,类似
}
}
for(int j=0;j<=K;j++)
f[j][x]=tmp[j];

	sz[x]=up;
} 

}

int main()
{
fri fro
ll T;
scl(T);
while(T–)
{
scl(n);scl(K);
fin a[i].clear();
mem(sz,0,sf sz);
r=0;
for(int i=1;i<n;i++)
{
ll u,v;o tp;
scl(u);
scl(v);
scl(tp.a);
scl(tp.b);
tp.x=v;
a[u].push_back(tp);
tp.x=u;
a[v].push_back(tp);
r+=max(tp.a,tp.b);
}
//dfsz(1,0);
fin sz[i]–;
l=0;//r=1000000000000000;
inf=100000000000000000;
while(l<=r)
{
mid=(l+r)/2;
dfs(1,0);
if(f[K][1]<inf)
{
// cout<<mid<<" “<<f[K][1]<<endl;
ans=mid;
r=mid-1;
}
else
l=mid+1;
}
printf(”%lld\n",ans);
}

//printf(“Time used=%.2f\n”,(double)clock()/CLOCKS_PER_SEC);
return 0;
}
`

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值