前言

复习笔记第6篇。

求直径的两种方法

树形DP:

dfs(y); ans=max( ans,d[x]+d[y]+w[i] ); d[x]=max( d[x],d[y]+w[i] );

               int dis=dfs( v,u )+1;
if ( f[u]<dis ) g[u]=f[u],f[u]=dis;
else if ( g[u]<dis ) g[u]=dis;
ans=max( ans,f[u]+g[u]+1 );
return f[u];

两次 bfs/dfs:

从任意点出发,找到最远点l;

从 l 出发,找到最远点 r. \(l\to r\) 即为所求。

0——P4408 [NOI2003]逃学的小孩

link

题意

\(n\) 点 \(m\) 边的带权无向图,任意两点之间有且仅有一条通路。有三个点:A,B,C,从 C 出发,先去 AB 中较近的一个,如果没找到,再去另一个,不给出具体的 ABC,问最坏情况下要多长时间。

思路

题目要求就是在一棵树上找到3个点 \(A\) 、\(B\)、\(C\) 令 \(AB+BC\) 最大,同时要满足 \(AC>AB\)。

由于要最大化这个距离,一个很明显的想法就是让其中一条成为直径,设为 \(AB.\) 然后再找另一条 \(BC\) 即可。但是要满足 \(AC>AB\) ,所以就是:先找直径 \(AB\) ,然后找一点 \(C\) 使得 \(min(AC,BC)\) 取得最大值,\(ans=\) 直径 \(+min(AC,BC)=\) 直径 \(+BC.\)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
struct edge
{
int to,nxt;ll val;
}e[N*10];
int n,m,head[N],tot=0,vis[N];
ll dis[N],dis2[N]; void add( int u,int v,ll w )
{
tot++; e[tot].to=v; e[tot].nxt=head[u]; e[tot].val=w; head[u]=tot;
} int bfs( int S )
{
memset( dis,0,sizeof(dis) ); memset( vis,0,sizeof(vis) );
queue<int> q;
while ( !q.empty() ) q.pop();
q.push( S ); vis[S]=1; int res=0,num=0;
while ( q.size() )
{
int u=q.front(); q.pop();
for ( int i=head[u]; i; i=e[i].nxt )
{
int v=e[i].to;
if ( vis[v] ) continue;
vis[v]=1; dis[v]=dis[u]+e[i].val; q.push( v );
if ( dis[v]>res ) res=dis[v],num=v;
}
}
return num;
} int main()
{
scanf( "%d%d",&n,&m );
for ( int i=1; i<=m; i++ )
{
int u,v; ll w; scanf( "%d%d%lld",&u,&v,&w );
add( u,v,w ); add( v,u,w );
} int l=bfs(1),r=bfs(l); ll ans=dis[r],res=0;
for ( int i=1; i<=n; i++ )
dis2[i]=dis[i];
bfs( r );
for ( int i=1; i<=n; i++ )
res=max( res,min(dis[i],dis2[i]) ); printf( "%lld",ans+res );
}

1——P2491 [SDOI2011]消防

link

题意

\(n\) 个城市,任意两个都连通且有唯一路径,每条连通两个城市的道路的长度为 \(z_i\). 在一条边长度和不超过 \(s\) 的路径(两端都是城市)上建立消防枢纽,要求其他所有城市到这条路径的距离的最大值最小。求枢纽位置。

思路

每一个点到树上最远的点一定在直径上.所以可以直接枚举直径上的点,然后在找直径上的点到其他点的距离最大是多少.一个很显然的想法是,在直径上取的距离越大越优,那么可以直接枚举起点。

但是 \(N^2\) 复杂度还是不够。显然,答案具有单调性(不超过 s 的情况下越长越好),那么可以通过双指针,单调队列优化,\(O(n)\) 实现这个过程。当然也可以二分不过要带一个 \(\log.\)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=3e5+10;
const ll inf=1e15+10;
struct edge
{
ll fro,to,nxt; ll val;
}e[N<<1];
ll n,s,tot,head[N],dis[N],pre[N],bet[N],l,r;
ll ans=inf,sum[N],dis1[N],que[N]={inf},t=0,h=0; void add( ll u,ll v,ll w )
{
e[++tot].fro=u; e[tot].to=v; e[tot].val=w; e[tot].nxt=head[u]; head[u]=tot;
} void dfs( ll u,ll fa,ll sum,bool sav )
{
if ( sav ) pre[u]=fa,bet[u]=sum;
dis[u]=dis[fa]+sum;
for ( ll i=head[u]; i; i=e[i].nxt )
if ( e[i].to!=fa ) dfs( e[i].to,u,e[i].val,sav );
} void get_path()
{
dfs( 1,0,0ll,0 ); ll mx=0;
for ( ll i=1; i<=n; i++ )
if ( dis[i]>mx ) l=i,mx=dis[i];
dfs( l,0,0ll,1 ); mx=0;
for ( ll i=1; i<=n; i++ )
if ( dis[i]>mx ) r=i,mx=dis[i];
} void bfs()
{
memset( dis,63,sizeof(dis) ); queue<ll> q,fro;
for ( ll i=r; i; i=pre[i] )
q.push(i),fro.push(i),dis[i]=0;
while ( !q.empty() )
{
ll v=q.front(),u=fro.front(); q.pop(); fro.pop();
for ( ll i=head[v]; i; i=e[i].nxt )
if ( dis[e[i].to]>=inf )
{
dis[e[i].to]=dis[v]+e[i].val;
dis1[u]=max( dis1[u],dis[e[i].to] );
q.push( e[i].to ); fro.push( u );
}
}
} int main()
{
scanf( "%lld%lld",&n,&s );
for ( ll i=1,u,v,w; i<n; i++ )
scanf( "%lld%lld%lld",&u,&v,&w ),add( u,v,w ),add( v,u,w ); get_path(); bfs();
pre[n+1]=r;
for ( ll i=n+1; i; i=pre[i] )
sum[pre[i]]=sum[i]+bet[i];
for ( ll L=r,R=r; L && R!=l; L=pre[L] )
{
ll las=R; h++;
while ( sum[R]-sum[L]<=s && R )
{
las=R; R=pre[R];
if ( R && sum[R]-sum[L]<=s )
{
while ( dis1[R]>=que[t] && t>=h ) t--;
que[++t]=dis1[R];
}
}
if ( R==0 || sum[R]-sum[L]>s ) R=las;
ll tmp=max( sum[L],sum[l]-sum[R] ); tmp=max( tmp,que[h] );
ans=min( tmp,ans );
} printf( "%lld",ans ); return 0;
}

2——P2610 [ZJOI2012]旅游

link

题意

T国的国土可以用一个凸N边形来表示,包含 \(N-2\) 个城市,每个城市都是顶点为 \(N\) 边形顶点的三角形,两人的旅游路线可以看做是连接N个顶点中不相邻两点的线段。问一路能经过最多多少城市。

一个城市被当做经过当且仅当其与线路有至少两个公共点。

思路

很巧妙的一道题。(不愧是ZJOI)

三角剖分是个很有意思的信息。不是让你想递推啊喂

考虑什么样的城市是不满足三角剖分的。会发现,不可能存在一些城市围成一圈(这样有一个点就会在内部而不是端点),所以如果将相邻的城市连边,是不可能存在环的。没有环,又是连通的,那么就是树了。

问题就转化成了求树的直径,裸题。

然而事实上,建图有亿点麻烦...但是非常幸运的是,由于这道题是三角形,所以肯定是二叉树,那么我们有pair!我们有 map!我们有STL!重点是我们有O2!

于是这道题就做完了。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct edge
{
int to,nxt;
}e[N<<1];
int head[N],tot=0,n,s[N][3],ans,g[N],f[N];
map<pair<int,int>,int> mp; void add( int u,int v )
{
e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot;
e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot;
} int dfs( int u,int fa )
{
for ( int i=head[u]; i; i=e[i].nxt )
{
int v=e[i].to;
if ( v==fa ) continue;
int dis=dfs( v,u )+1;
if ( f[u]<dis ) g[u]=f[u],f[u]=dis;
else if ( g[u]<dis ) g[u]=dis;
}
ans=max( ans,f[u]+g[u]+1 );
return f[u];
} void build( int i,int j,int u )
{
pair<int,int> pr=make_pair( i,j );
if ( mp[pr] ) add( u,mp[pr] );
else mp[pr]=u;
} int main()
{
scanf( "%d",&n );
for ( int i=1; i<=n-2; i++ )
{
scanf( "%d%d%d",&s[i][0],&s[i][1],&s[i][2] ); sort( s[i],s[i]+3 );
build( s[i][0],s[i][1],i ); build( s[i][1],s[i][2],i ); build( s[i][0],s[i][2],i );
} dfs( 1,0 );
printf( "%d",ans ); return 0;
}

3——P3629 [APIO2010]巡逻

link

题意

有 \(n\) 个村庄,编号为 \(1, 2, ..., n\) 。有 \(n – 1\) 条道路连接着这些村 庄,从任何一个村庄都可以到达其他任一个村庄。道路长度均为 1。 巡警车每天要到所有的道路上巡逻。警察局设在编号为 \(1\) 的村庄里,每天巡警车总是从警察局出发又回到警察局。

在这些村庄之间建 \(K\) 条新的道路, 可以连接任意两个村庄。每天巡警车必须 经过新建的道路正好一次. 求最小的巡逻距离。

思路

考虑逐条加边。

如果不加边,那么答案显然是 \(2(n-1)\).

如果加一条边,由于必须经过恰好一次,所以在沿着新的道路 \((u,v)\) 走了一次之后,要返回 \(u\) ,必须沿着树上的环的另一半再走一遍,那么这时候 \(u\to v\) 的路径只需要走一次,所以 \(ans=2(n-1)-L-+1.\)

再加一条边,如果环没有重叠,那么按照一条的情况处理即可。否则,重叠部分不会被走过,所以还要走一次,又变成了需要走两次的边。

总结两种情况,得到算法:

  1. 找一遍直径,边权取反,长度为 \(L_1\)
  2. 再求直径,得到 \(L_2\)
  3. \(ans=2(n-1)-(L_1-1)-(L_2-1)\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct edge
{
int to,nxt,val;
}e[N<<1];
int n,k,tot=0,mx,head[N],dis[N],pre[N],f[N];
bool vis[N];
queue<int> q; void add( int u,int v,int w )
{
e[++tot].to=v; e[tot].val=w; e[tot].nxt=head[u]; head[u]=tot;
} int bfs( int s )
{
memset( dis,0x3f,sizeof(dis) );
q.push( s ); dis[s]=pre[s]=0;
while ( q.size() )
{
int t=q.front(); q.pop();
for ( int i=head[t]; i; i=e[i].nxt )
if ( dis[e[i].to]==0x3f3f3f3f )
dis[e[i].to]=dis[t]+e[i].val,pre[e[i].to]=i,q.push( e[i].to );
}
int res=1;
for ( int x=1; x<=n; x++ )
if ( dis[x]>dis[res] ) res=x;
return res;
} void dp( int x )
{
vis[x]=1;
for ( int i=head[x]; i; i=e[i].nxt )
if ( !vis[e[i].to] )
{
dp( e[i].to );
mx=max( mx,f[e[i].to]+f[x]+e[i].val );
f[x]=max( f[x],f[e[i].to]+e[i].val );
}
} int main()
{
memset( head,0,sizeof(head) ); tot=1; scanf( "%d%d",&n,&k );
for ( int i=1,u,v; i<n; i++ )
scanf( "%d%d",&u,&v ),add( u,v,1 ),add( v,u,1 ); int l=bfs( 1 ); l=bfs(l);
int L1=dis[l],fl=1; mx=0;
if ( k==2 )
{
for ( ; pre[l]; l=e[pre[l]^1].to )
e[pre[l]].val=e[pre[l]^1].val=-1;
dp( 1 ); fl=2;
}
printf( "%d",2*(n-1)-L1-mx+fl ); return 0;
}

4——P4381 [IOI2008]Island

link

题意

\(N\) 个岛屿组成,从每个岛屿 \(i\) 出发向另外一个岛屿建了一座长度为 \(L_i\) 的桥,可以双向行走。同时,每对岛屿之间都有一艘专用的往来两岛之间的渡船。你希望经过的桥的总长度尽可能长,但受到以下的限制:

  • 可以自行挑选一个岛开始游览。
  • 任何一个岛都不能游览一次以上。
  • 任何时间都可以由当前所在的岛 \(S\) 去另一个从未到过的岛 \(D\)。从 \(S\) 到 \(D\) 有如下方法:
    • 步行:仅当两个岛之间有一座桥,桥长会累加到步行总距离中。
    • 渡船:仅当没有任何桥和以前使用过的渡船的组合可以由 \(S\) 走到 \(D\) (检查是否可到达时应该考虑所有路径,包括经过曾游览过的岛)。

注意,你不必游览所有的岛,也可能无法走完所有的桥。

给定 \(N\) 座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的长度之和的最大值。

思路

每个岛屿一座桥,\(n\) 个岛屿 \(n\) 座桥……诶,不是树?出大问题

于是你不幸地发现,这是基环树,而且还是基环树森林!不过没有关系。由题意可知,如果乘船离开一棵基环树就不能再回来了。(因为回来要坐船,然而你坐过了,所以可达性成立,你就不能坐船了)

于是题目就变成了,求所有基环树的直径之和。

首先,找出基环树的环。然后,对于直径,显然分成两部分,要么在去掉环的某棵子树内,要么是环上一段距离加上两棵不同子树内的距离。

于是可以先预处理出环上每个点在去掉环的前提下,子树内的直径。然后把环展开,分顺时针和逆时针两种情况讨论,每次的 \(res=dis(i,j)+d[i]+d[j]\) ,取max 即可。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
int n,m,cnt=0,dfn[N],fa[N],pre[N],a[N<<1];
int head[N],tot=1,q[N<<1];
ll ans=0,d[N<<1],f[N],b[N<<1],res;
bool vis[N]; struct edge
{
int nxt,to,val;
}e[N<<1]; void add( int u,int v,int w )
{
e[++tot]=(edge){head[u],v,w}; head[u]=tot;
} void bfs1( int u ) //找环
{
int tail=0; q[++tail]=u;
while ( tail )
{
int he=q[tail--]; dfn[he]=++cnt;
for ( int i=head[he]; i; i=e[i].nxt )
{
int v=e[i].to;
if ( i==(pre[he]^1) ) continue;
if ( !dfn[v] ) { fa[v]=he,pre[v]=i,q[++tail]=v; }
else if ( !m )
{
int p=he;
for ( ; p!=v; p=fa[p] )
a[++m]=p,d[m]=e[pre[p]].val,vis[p]=1;
a[++m]=v; d[m]=e[i].val; vis[v]=1;
}
}
}
} ll bfs( int x )
{
int h=1,t=0; q[++t]=x;
while ( h<=t )
{
int u=q[h++];
for ( int i=head[u]; i; i=e[i].nxt )
{
int v=e[i].to;
if ( vis[v] ) continue;
fa[v]=u; vis[v]=1; q[++t]=v; pre[v]=e[i].val;
}
}
for ( int i=t; i>1; i-- )
{
int v=q[i],u=fa[v],w=pre[v];
res=max( res,f[u]+f[v]+w );
f[u]=max( f[u],f[v]+w );
}
return f[x];
} void solve( int x )
{
m=res=0; bfs1( x );
if ( !m ) { bfs( x ); ans+=res; return; }
reverse( a+1,a+1+m ); reverse( d+1,d+1+m ); //反转
for ( int i=1; i<=m; i++ )
b[i]=bfs( a[i] );
for ( int i=m+1; i<=m*2; i++ ) //复制,展环成链
a[i]=a[i-m],b[i]=b[i-m],d[i]=d[i-m];
for ( int i=1; i<=m*2; i++ )
d[i]+=d[i-1];
int h=0,t=0; q[0]=0;
for ( int i=1; i<=m*2; i++ )
{
while ( h<=t && i-q[h]+1>m ) h++;
if ( h<=t ) res=max( res,b[i]+b[q[h]]+d[i]-d[q[h]] );
while ( h<=t && b[q[t]]-d[q[t]]<=b[i]-d[i] ) t--;
q[++t]=i;
}
ans+=res;
} int main()
{
scanf( "%d",&n );
for ( int i=1,v,w; i<=n; i++ )
scanf( "%d%d",&v,&w ),add( v,i,w ),add( i,v,w ); for ( int i=1; i<=n; i++ )
if ( !dfn[i] ) solve( i ); printf( "%lld\n",ans );
}

以上是树的直径部分。

5——P5021 赛道修建

link

题意

有一棵带边权的树,在树上选出 \(m\) 条互不相交的链(点可以重合,但是边不能重合),使得 \(m\) 条链中最短的链最长。

思路

看到这个很容易想到总体思路是二分。

考虑如何判定。然后发现这道题其实藏了一个贪心:对于一棵子树内的所有链,在有最多的儿子对答案做出贡献的前提下,最大化 \(f_i\) 的值。由于 一个点的 \(f\) 最多对答案产生 \(1\) 的贡献,所以让儿子更少贡献,转移更大的 \(f_i\) 不会变优。

那么正解就来了。首先二分出一个 \(mid\) 。然后对于一棵子树,令 \(f_i\) 为以 \(i\) 为根的子树中,最优的不完整的链长(完整的根据上面的贪心分析,已经 \(\ge mid\) ,贡献到答案里去了;不完整就是还要和别的链拼接的)。

暂时不考虑根节点 \(i\) ,对于 \(i\) 所有的儿子,如果能单独贡献则直接计入答案。否则,尝试两两合并这些子链(见题意,点是可以重合的,在根节点合并多少都没有关系),如果长度 \(\ge mid\) 就计入答案。如果都不行,那就在这些剩余的链中选取最长的一条,计入 \(f_i\) ,尝试往上走。于是就可以把所有子节点的 \(f_j\) 排序,贪心找最大的匹配数(能匹配的几条中最小的一个),然后把剩下的转移给 \(f_i\) 即可。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e4+10;
struct edge
{
int to,nxt; ll val;
}e[N<<1];
int head[N],tot=0,n,m;
ll f[N],subans[N];
vector<int> son[N]; void add( int u,int v,ll w )
{
e[++tot].to=v; e[tot].nxt=head[u]; e[tot].val=w; head[u]=tot;
} int get_ans( int u,int pos,int cnt,ll x )
{
int res=0,l=0;
for ( int r=cnt-1; r; r-- )
{
r-=(r==pos);
while ( l<r && son[u][l]+son[u][r]<x ) ++l;
l+=(l==pos);
if ( l>=r ) break;
res++; l++;
}
return res;
} void dfs( int u,int fa,ll x )
{
f[u]=subans[u]=0; son[u].clear();
for ( int i=head[u]; i; i=e[i].nxt )
{
int v=e[i].to;
if ( v==fa ) continue;
dfs( v,u,x ); f[v]+=e[i].val;
if ( f[v]>=x ) subans[u]++;
else son[u].push_back( f[v] );
}
int cnt=son[u].size(); sort( son[u].begin(),son[u].end() );
int l=0,r=cnt,sub=0,res;
for ( int r=cnt-1; r; r-- ) //配对
{
while ( l<r && son[u][l]+son[u][r]<x ) l++;
if ( l>=r ) break;
sub++; l++;
}
subans[u]+=sub;
if ( sub*2==cnt ) return;
l=0; r=cnt-1;
while ( l<=r ) //二分找最大的一个mid,使得首先满足子树要求
{
int mid=(l+r)>>1;
int tmp=get_ans( u,mid,cnt,x );
if ( tmp==sub ) res=mid,l=mid+1;
else r=mid-1;
}
f[u]=son[u][res]; //记入f[i]
} bool check( ll x )
{
int res=0; dfs( 1,0,x );
for ( int i=1; i<=n; i++ ) //每个点的贡献
res+=subans[i];
return res>=m;
} int main()
{
ll l=0,r=0;
scanf( "%d%d",&n,&m );
for ( int i=1; i<n; i++ )
{
int u,v; ll w; scanf( "%d%d%lld",&u,&v,&w );
add( u,v,w ); add( v,u,w ); r+=w;
} r/=(ll)m; ll ans=0;
while ( l<=r )
{
ll mid=(l+r)>>1;
if ( check(mid) ) ans=mid,l=mid+1;
else r=mid-1;
} printf( "%lld",ans ); return 0;
}

6——P1852 跳跳棋

link

题意

跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。棋盘上有3颗棋子,分别在 \(a,b,c\) 这三个位置。我们要通过最少的跳动把他们的位置移动成 \(x,y,z\) 。(棋子是没有区别的)

跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过1颗棋子。

判断是否可以完成任务。如果可以,输出最少需要的跳动次数。

思路

神仙题……非常巧妙地建模。只能说:女少口阿

首先,对于中轴棋子为 \(b\) (中间那个)的情况,显然一直往中间跳可以一直减小范围,直到不能跳为止。这时候就得到了一个非常有用的“Basic” 状态,也就是“根状态”(这怎么跟某道字符串手玩题这么像啊)

然后把 \(b\) 往左右跳的情况看成左右节点状态,那么所有状态构成了一棵二叉树。对于棋盘上所有的 \(a,b,c\) ,状态构成了一个森林。

那么,如果 \((a,b,c)\to (x,y,z)\) ,首要条件是在同一棵树上。这样第一问就解决了。

考虑状态怎么去树根。利用 LCA 的思想,把两个状态到根的距离调整到一样,然后二分向上的步数,最后找到一个 \(L\) 使得两个状态向上 \(L\) 步相遇,那么总答案就是 高度差加上二分答案的两倍。

代码

#include <bits/stdc++.h>
using namespace std;
const int inf=1e9+7;
int sx,sy,sz,dep,mx; void init( int &x,int &y,int &z )
{
x+=inf; y+=inf; z+=inf;
if ( y>z ) swap( y,z );
if ( x>y ) swap( x,y );
if ( y>z ) swap( y,z );
} void dfs( int x,int y,int z,int step )
{
int del1=y-x,del2=z-y;
if ( step==mx || del1==del2 ) { sx=x,sy=y,sz=z; dep=step; return; }
if ( del1>del2 )
{
swap( del1,del2 ); int del=del2/del1;
if ( del2%del1==0 ) del--;
if ( step+del<=mx ) dfs( x,y-del*del1,z-del*del1,step+del );
else dfs( x,y-(mx-step)*del1,z-(mx-step)*del1,mx );
}
else
{
int del=del2/del1; del-=(del2%del1==0);
if ( step+del<=mx ) dfs( x+del*del1,y+del*del1,z,step+del );
else dfs( x+(mx-step)*del1,y+(mx-step)*del1,z,mx );
}
} int main()
{
int x,y,z,a,b,c;
scanf( "%d%d%d",&a,&b,&c ); init( a,b,c );
scanf( "%d%d%d",&x,&y,&z ); init( x,y,z ); mx=inf;
dfs( a,b,c,0 ); int sa=sx,sb=sy,sc=sz,sd=dep;
dfs( x,y,z,0 );
if ( sx!=sa || sy!=sb || sz!=sc ) { printf( "NO" ); return 0; }
printf( "YES\n" );
//------------query1-------------------
int ans=0;
if ( sd>dep )
{
ans=sd-dep; mx=sd-dep;
dfs( a,b,c,0 ); a=sx; b=sy; c=sz;
}
if ( sd<dep )
{
ans=dep-sd; mx=dep-sd;
dfs( x,y,z,0 ); x=sx,y=sy,z=sz;
} int l=0,r=inf;
while ( l<=r )
{
mx=(l+r)>>1;
dfs( a,b,c,0 ); sa=sx,sb=sy,sc=sz;
dfs( x,y,z,0 );
if ( sa!=sx || sb!=sy || sc!=sz ) l=mx+1;
else r=mx-1;
} printf( "%d",(l<<1)+ans );
}

Last

To be continue...

树的直径,LCA复习笔记的更多相关文章

  1. 【bzoj3362/3363/3364/3365】[Usaco2004 Feb]树上问题杂烩 并查集/树的直径/LCA/树的点分治

    题目描述 农夫约翰有N(2≤N≤40000)个农场,标号1到N,M(2≤M≤40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地图一样, 图中农场用F ...

  2. 树链剖分 树剖求lca 学习笔记

    树链剖分 顾名思义,就是把一课时分成若干条链,使得它可以用数据结构(例如线段树)来维护 一些定义: 重儿子:子树最大的儿子 轻儿子:除了重儿子以外的儿子 重边:父节点与重儿子组成的边 轻边:除重边以外 ...

  3. BZOJ 1912(树的直径+LCA)

    题面 传送门 分析 显然,如果不加边,每条边都要走2次,总答案为2(n-1) 考虑k=1的朴素情况: 加一条边(a,b),这条边和树上a->b的路径形成一个环,这个环上的边只需要走一遍,所以答案 ...

  4. 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分

    树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...

  5. 51nod 1766 树上的最远点对 | LCA ST表 线段树 树的直径

    51nod 1766 树上的最远点对 | LCA ST表 线段树 树的直径 题面 n个点被n-1条边连接成了一颗树,给出a~b和c~d两个区间,表示点的标号请你求出两个区间内各选一点之间的最大距离,即 ...

  6. XTU1267:Highway(LCA+树的直径)

    传送门 题意 有n个小镇,Bobo想要建造n-1条边,并且如果在u到v建边,那么花费是u到v的最短路长度(原图),问你最大的花费. 分析 比赛的时候没做出来,QAQ 我们首先要找到树的直径起点和终点, ...

  7. [51nod 1766]树上的最远点对 (树的直径+ST表求lca+线段树)

    [51nod 1766]树上的最远点对 (树的直径+ST表求lca+线段树) 题面 给出一棵N个点的树,Q次询问一点编号在区间[l1,r1]内,另一点编号在区间[l2,r2]内的所有点对距离最大值.\ ...

  8. 树形DP 学习笔记(树形DP、树的直径、树的重心)

    前言:寒假讲过树形DP,这次再复习一下. -------------- 基本的树形DP 实现形式 树形DP的主要实现形式是$dfs$.这是因为树的特殊结构决定的——只有确定了儿子,才能决定父亲.划分阶 ...

  9. Java基础复习笔记基本排序算法

    Java基础复习笔记基本排序算法 1. 排序 排序是一个历来都是很多算法家热衷的领域,到现在还有很多数学家兼计算机专家还在研究.而排序是计算机程序开发中常用的一种操作.为何需要排序呢.我们在所有的系统 ...

随机推荐

  1. 六:Redis配制文件

    1.它在哪儿 1.1 安装包解压开里面就会有redis.conf 1.2 我们在修改一定要拷贝一份,修改拷贝的那一份 2.Units单位 2.1 对于单位来说配制开头定义了,1k和1kb是不一样的,同 ...

  2. Golang中的OO(面向对象)

    组合 > 继承 Go中的设计,以为继承的被诟病,所以Golang的设计团队在语言的设计时并没有采用经典的OO模式 那Golang中各个部分是怎么联系到一起的呢 type ReadWriter i ...

  3. 一篇文章了解_unittest

    1. 基本概念 2018年10月7日 星期日 11:39 unittest是python自带的单元测试框架,有时候又被称为"PyUnit",是python版本的JUint实现. 该 ...

  4. netcore一键部署到linux服务器以服务方式后台运行

    @font-face { font-family: octicons-link; src: url("data:font/woff;charset=utf-8;base64,d09GRgAB ...

  5. uniapp开发小程序

    uniapp开发小程序 uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS.Android.Web(响应式).以及各种小程序(微信/支付宝/百度/头条 ...

  6. 新同事不讲“码”德,这SQL写得太野了,请耗子尾汁~

    今天来分享几个MySQL常见的SQL错误(不当)用法.我们在作为一个初学者时,很有可能自己在写SQL时也没有注意到这些问题,导致写出来的SQL语句效率低下,所以我们也可以自省自检一下. 1. LIMI ...

  7. Xshell不能连接Kali系统SSH的解决

    修改sshd_config文件 vim /etc/ssh/sshd_config 将#PasswordAuthentication yes的注释去掉 将#PermitRootLogin prohibi ...

  8. 这份java多线程笔记,你真得好好看看,我还没见过总结的这么全面的

    1.线程,进程和多线程 1.程序:指指令和数据的有序集合,其本身没有任何意义,是一个静态的概念 2.进程:指执行程序的一次执行过程,是一个动态的概念.是系统资源分配的单位(注意:很多多线程是模拟出来的 ...

  9. 如何使用Camtasia给视频或者图片调色

    喜欢摄影过着做视频的朋友一定知道,一张好看的照片或者一段精美视频的构成因素很多,取景本身肯定是个很重要的条件,相机的素质是非常重要的硬件条件,接下来的就是后期的编辑和处理了,而在后期处理过程中调色就显 ...

  10. 【PUPPETEER】初探之执行JavaScript方法(六)

    一.知识点 page.evaluate() document.querySelector().value = ''; 二.解析知识点 page.evaluate(),查看puppeteer 的api ...