51nod1803 森林直径


题目看这里
(为什么xhEditor不能支持字体了,不管了,那就用markdown吧)

一个非常有意思的题目,首先考虑离线做法,把所有的询问按照l排序,所有的边倒序插入整个树中
我们需要维护两个东西:
1.f[x][i]表示当整颗树有[x,f[x][i]]这一个区间内的所有边时,以x为根的子树存在一个深度为i的儿子,满足条件最小的f[x][i]
2.g[k]表示最小的r,使得[l,r]组成的森林拥有长度为k的直径
那么每次加入一条边(x,y) (x为y的祖先) 我们就需要更新这两个东西:
g[i+j+1]=min(g[i+j+1],max(f[x][i],f[y][j],y-1)) (注意y-1就是这条边的编号)
f[x][i]=min(f[x][i],max(y-1,f[y][j-1])
注意到题目的性质,树高是log n级别的
所以一次操作是O(log n) 的,一共n次加边,总复杂度O(n lg n)

(再也弄不回原来的code风格了,否则就是没有高亮的了)

#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define M 35
#define N 500010
using namespace std;
struct Q{ int l,r; } s[N];
int n,m,f[N][M],g[M<<2],p[N],A;
inline bool cmp(Q a,Q b){ return a.l==b.l?a.r>b.r:a.l>b.l; }
inline void adj(int x,int y){
    for(int i=0;i<M;++i) if(f[x][i]<n)
        for(int j=0;j<M;++j) g[i+j+1]=min(g[i+j+1],max(max(f[x][i],f[y][j]),y-1));
    for(int i=0;i<M;++i) f[x][i]=min(f[x][i],max(f[y][i-1],y-1));
}
int main(){
    scanf("%d",&n); memset(f,0x3f,sizeof f);
    for(int i=1;i<n;++i) scanf("%d%*d",p+i),f[i][0]=0;
    scanf("%d",&m); f[n][0]=0;
    for(int i=1;i<=m;++i) scanf("%d%d",&s[i].l,&s[i].r);
    sort(s+1,s+1+m,cmp); memset(g,0x3f,sizeof g);
    for(int i=n-1,j=1;i&&j<=m;--i){
        adj(p[i],i+1);
        for(int k=M*2;j<=m&&s[j].l==i;++j)
            for(++k;;--k) if(g[k]<=s[j].r) { A+=k; break; }
    }
    printf("%d
",A);
}

原文地址:https://www.cnblogs.com/Extended-Ash/p/9477080.html