【题目描述】
Tar国正在准备每年一次的巡游活动。国王将会在一个城市S里召集人群,沿着城市间的道路进行游览,最终在一个城市T里发表他每年一次的著名演讲。
Tar国有N个城市,由于国家的特殊要求,每两个城市之间存在一条唯一的简单通路。 国王希望借着这个机会视察Tar国的城市建设,因此他提出S到T的距离不能少于L条道路。 同时,国王的私人医生检查了他的身体情况后,断定国王的身体不适合做长途旅行,因此他要求S到T的距离不能多于R条道路。
另外,政府希望跟随国王的人民沿途不仅能看到城市风景,还能看到城市外的美丽乡村。因此每条道路定义了一个魅力值Ci,一条路径的魅力值定义为这条路径的中位数。更详细的说法是这样的: 将路径上所有边的魅力值排序,得到序列{Ai}。假设i=2k+c(0<=c<=1),中位数就是A(k+1)。 你的任务就是求出魅力值最大的路径,并输出这个魅力值。【Sample Input】
(第一行是三个整数N,L,R,表示Tar国的城市个数、路径的最小和最大长度。
接下来N-1行,每行3个整数Ai,Bi,Ci,表示有一条连接Ai和Bi且魅力值Ci的道路。)
{样例输入1}
5 1 4
1 2 1
1 3 4
3 4 7
3 5 2
{样例输入2}
6 3 4
1 2 1
2 3 1
3 4 1
4 5 2
5 6 2【Sample Output】
{样例输出1} 7
{样例输出2} 2【数据范围】
{20%} N<=100。
{65%} N<=30000。这部分数据的时间限制是2s,其余数据是4s。
存在着大约20%的数据,树的每个节点度不超过2。
{100%}N<=10^5,1<=L<=R<=N-1, 1<=Ci<=10^9
【题解】二分+树分治+单调队列
题目大意就是要求一条路径使得其中位数最大。首先由于答案很难直接确定,采用二分~
{20%} 首先二分出可能答案mid。有了mid值之后对于每条边w[i],若w[i]>=mid,令w[i]=1,else w[i]=-1
这样一来,问题被转化为树上是否存在一条长度在[L,R]之间的非负路径,暴力判断即可;
{65%} 二分+树分治+线段树:
依然如20%做法找非负路径。但这里采用树分治的方法。对于每棵子树我们只考虑经过根的路径,其余递归处理。
考虑过根的路径。对于当前递归到的子树x,依次处理其儿子子树的信息并与之前儿子子树得到的信息算出答案。那么我们要保存的值是什么呢?
如果我们对于每个节点保存一个深度de[i],从点i到当前根节点的路径值为sum[x],那么对于已处理过的所有节点的深度相同的点,只需要保存路径最大值即可。因为相同深度的点对答案贡献的范围是一样的(都在[L,R]之间),而要找非负路径显然最大值更优。
得到这个性质之后,考虑怎么更新答案。遍历当前子树,对于每个节点u,合法的答案就是sum[u]+max(p[v]) 其中p[v]代表深度为v的路径最大值,v代表对于当前点距离范围在[L,R]之间的深度。而区间最大值,我们显然能用线段树来做。这样每次树分治我们构造一棵线段树,复杂度为 O(log N),树分治复杂度为 O(N log N),二分复杂度为 O(log N)。总时间复杂度:O(N log N^3)。
{100%} 二分+树分治+单调队列
在60%的基础上,我们思考能否降低查询需求区间最大值的复杂度。如果对于当前子树遍历时使用广搜,那么深度显然是逐渐增大的,此时我们可以用单调队列来记录最大值。
但是这个方法要注意,单调队列里保存的最多是当前子树的最大深度个节点,所以如果处理的第一棵子树很大的话,复杂度容易退化。为了保证复杂度,可以在二分前先树分治做一遍预处理,使得子树按照深度从小到大排序。
时间复杂度:O(N log N^2);
//但是我懒,我不想打初始化,所以我树分治加了个剪枝过的。如果当前子树大小小于L就返回。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define N 100005
#define inf 1000000000
struct edge{ int to,nxt,s,w;}e[N<<1];
int n,L,R,cnt,ans,m,mx,rt,tot,l=1,r,w[N],fi[N],s[N],p[N],q[N],f[N],g[N],de[N],fa[N];
bool bo[N];
void add(int u,int v,int w)
{
e[++cnt].to=v;e[cnt].s=w;
e[cnt].nxt=fi[u];fi[u]=cnt;
}
void findrt(int x)
{
s[x]=1;int mxx=0;
for (int i=fi[x];i;i=e[i].nxt)
if (!bo[e[i].to] && e[i].to!=fa[x])
{
fa[e[i].to]=x;
findrt(e[i].to);
s[x]+=s[e[i].to];
mxx=std::max(mxx,s[e[i].to]);
}
mxx=std::max(mxx,tot-s[x]);
if (mxx<mx) mx=mxx,rt=x;
}
bool work(int x)
{
int mxdp=0,h,t,he,ta;f[0]=0;
for (int i=fi[x];i;i=e[i].nxt)
{
if (bo[e[i].to]) continue;
h=1;t=0;
for (int j=mxdp;j>=L;--j)
{
for (;h<=t && f[p[t]]<=f[j];--t);
p[++t]=j;
}
de[e[i].to]=1;g[1]=e[i].w;fa[e[i].to]=x;
for (q[he=ta=1]=e[i].to;he<=ta;++he)
{
for (;h<=t && de[q[he]]+p[h]>R;++h);
if (de[q[he]]<=L)
{
for (;h<=t && f[p[t]]<=f[L-de[q[he]]];--t);
p[++t]=L-de[q[he]];
}
if (h<=t && f[p[h]]+g[he]>=0) return true;
if (de[q[he]]>=R) continue;
for (int j=fi[q[he]];j;j=e[j].nxt)
{
if (e[j].to==fa[q[he]] || bo[e[j].to]) continue;
fa[e[j].to]=q[he];
de[q[++ta]=e[j].to]=de[q[he]]+1;
g[ta]=g[he]+e[j].w;
}
}
mxdp=std::max(mxdp,de[q[ta]]);
for (int j=1;j<=ta;++j) f[de[q[j]]]=std::max(f[de[q[j]]],g[j]);
}
for (int i=0;i<=mxdp;++i) f[i]=-inf;
return false;
}
bool dfs(int x,int z)
{
if (s[x]>=L && work(x)) return true;
for (int i=fi[x];i;i=e[i].nxt)
if (!bo[e[i].to])
{
if (s[e[i].to]>s[x]) s[e[i].to]=z-s[x];
tot=mx=s[e[i].to];rt=0;
findrt(e[i].to);bo[rt]=true;
if (dfs(rt,s[e[i].to])) return true;
}
return false;
}
bool check(int mid)
{
for (int i=0;i<=n;++i) bo[i]=false,fa[i]=0,f[i]=-inf;
mx=n;tot=n;findrt(1);
bo[rt]=true;
for (int i=1;i<=cnt;++i)
if (e[i].s>=mid) e[i].w=1;
else e[i].w=-1;
return (dfs(rt,n));
}
int main()
{
scanf("%d%d%d\n",&n,&L,&R);
for (int i=1;i<n;++i)
{
int u,v;scanf("%d%d%d\n",&u,&v,&w[i]);
add(u,v,w[i]);add(v,u,w[i]);
}
std::sort(w+1,w+n);
mx=tot=n;findrt(1);ans=-1;
for (int i=1;i<=n;++i) f[i]=-inf;
for (r=n-1;l<=r;)
{
int mid=(l+r)>>1;
if (check(w[mid])) l=mid+1,ans=w[mid];
else r=mid-1;
}
printf("%d\n",ans);
return 0;
}
【题外话】我真是。。太菜了。。