#loj3046 [ZJOI2019]语言

本文探讨了树链剖分算法的优化,通过深入分析矩形面积并算法,提出了一种仅需两个log复杂度的方法。文章详细介绍了如何利用重链上的multiset优化线段树,以及如何进一步减少复杂度至O(nlogn)。

树链剖分入门题吧

一个非常直观的想法是使用树剖将一条链拆成\(log^2n\)个矩形,套用矩形面积并算法即可得到一个垃圾的3个log过不去算法

为了得到一个两个log的做法,我们观察一下拆出来的矩形的性质

首先是一堆跨越对角线的矩形,这一部分可以维护每个对角线处延伸出来的最大值线性得出

接下来如果我们令dfs序小的去数dfs序大的点,那么我们会发现矩形的第二维全部是重链的前缀

因此线段树可以被替换成每个重链上的multiset

此时的复杂度依然是3个log,仍然需要优化

接下来发现第一维是一段区间的矩形仅仅有\(O(nlogn)\)个,于是仅仅对这一部分开multiset维护,

剩余的都是前缀,倒着扫一遍重链然后使用一个变量记录最大值即可了

#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;const int N=1e5+10;typedef long long ll;
int v[N<<1];int x[N<<1];int ct;int al[N];ll ans;ll tot;
inline void add(int u,int V){v[++ct]=V;x[ct]=al[u];al[u]=ct;}
int dfn[N];int nfd[N];int df;int top[N];int fa[N];
int h[N];int siz[N];int dep[N];int n;int m;
inline int dfs1(int u,int f)
{
    for(int i=al[u];i;i=x[i])
        if(v[i]!=f)
            dep[v[i]]=dep[u]+1,fa[v[i]]=u,siz[u]+=dfs1(v[i],u),
            h[u]=(siz[h[u]]<siz[v[i]])?v[i]:h[u];
    return ++siz[u];
}
inline void dfs2(int u,int f)
{
    dfn[++df]=u;nfd[u]=df;top[u]=top[u]?top[u]:u;
    if(h[u])top[h[u]]=top[u],dfs2(h[u],u);
    for(int i=al[u];i;i=x[i])
        if(v[i]!=f&&v[i]!=h[u])dfs2(v[i],u);
}
namespace solver1
{
    int mx;int add;int len[N];
    inline void push(int x){mx=max(mx,x-add);}
    inline void ins(int st,int le){
    len[st]=max(len[st],le);}
    inline void solve()
    {
        for(int i=1;i<=n;i++)
        {push(len[i]);add--;ans+=max(0,mx+add);}
    }
}
struct data{int id;int len;};
inline int mabs(int x){return (x<0)?-x:x;}
struct adv_pq
{
    multiset <int> s;int mx;
    inline void clear(){s.clear();mx=0;}
    inline ll top()
    {return (s.empty())?mx:max(mx,*s.rbegin());}
    inline void push(data a)
    {
        tot-=top();
        if(a.id<0)s.insert(a.len);else mx=max(mx,a.len);
        tot+=top();
    }
    inline void pop(data a)
    {
        tot-=top();
        s.erase(s.find(a.len));
        tot+=top();
    }
};
namespace solver2
{
    vector <data> ins[N];vector <data> del[N];adv_pq su[N];
    inline void solvechain(int l,int r)
    {
        tot=0;
        for(int i=r;i>=l;i--)
        {
            for(vector <data> :: iterator it=ins[i].begin();it!=ins[i].end();++it)
                su[mabs(it->id)].push(*it);
            ans+=tot;
            for(vector <data> :: iterator it=del[i].begin();it!=del[i].end();++it)
                su[mabs(it->id)].pop(*it);
        }
        for(int i=r;i>=l;i--)
            for(vector <data> :: iterator it=ins[i].begin();it!=ins[i].end();++it)
                su[mabs(it->id)].clear();
    }
    inline void ins_rec(int fl,int fr,int id,int len)
    {
        if(top[dfn[fl]]==dfn[fl])
            ins[fr].push_back((data){id,len});
        else 
            ins[fr].push_back((data){-id,len}),
            del[fl].push_back((data){-id,len});
    }
}
int qu1[N];int qu2[N];int len1[N];int len2[N];
int hd1;int hd2;
inline void split(int u,int v)
{
    hd1=0;hd2=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])
            qu2[++hd2]=v,v=fa[top[v]];
        else 
            qu1[++hd1]=u,u=fa[top[u]];
    }
    if(nfd[u]<nfd[v])swap(u,v);
    if(hd1&&hd2)
    {
        if(nfd[top[qu1[hd1]]]>nfd[top[qu2[hd2]]])
        {
            for(int i=1;i<=max(hd1,hd2);i++)swap(qu1[i],qu2[i]);
            swap(hd1,hd2);
        }
    }
    for(int i=1;i<=hd1;i++)
    {
        int mu=qu1[i];int mv=top[mu];
        len1[i]=nfd[mu]-nfd[mv]+1;
        solver1::ins(nfd[mv],len1[i]);
        solver2::ins_rec(nfd[v],nfd[u],mv,len1[i]);
    }
    for(int i=1;i<=hd2;i++)
    {
        int mu=qu2[i];int mv=top[mu];
        len2[i]=nfd[mu]-nfd[mv]+1;
        solver1::ins(nfd[mv],len2[i]);
        solver2::ins_rec(nfd[v],nfd[u],mv,len2[i]);
    }
    solver1::ins(nfd[v],nfd[u]-nfd[v]+1);
    for(int i=1;i<=hd1;i++)
    {
        int mu=qu1[i];int mv=top[mu];
        for(int j=1;j<i;j++)
            solver2::ins_rec(nfd[mv],nfd[mu],top[qu1[j]],len1[j]);
        for(int j=1;j<=hd2;j++)
            solver2::ins_rec(nfd[mv],nfd[mu],top[qu2[j]],len2[j]);
    }
    for(int i=1;i<=hd2;i++)
    {
        int mu=qu2[i];int mv=top[mu];
        for(int j=1;j<i;j++)
            solver2::ins_rec(nfd[mv],nfd[mu],top[qu2[j]],len2[j]);
    }
}
bool book[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,u,v;i<n;i++)
        scanf("%d%d",&u,&v),add(u,v),add(v,u);
    dfs1(1,0);dfs2(1,0);
    for(int i=1,u,v;i<=m;i++)
        scanf("%d%d",&u,&v),split(u,v);
    solver1::solve();
    for(int i=1;i<=n;i++)
    {
        if(book[i])continue;
        int p=dfn[i];int cnt=0;
        for(;p;p=h[p])book[nfd[p]]=true,cnt++;
        solver2::solvechain(i,i+cnt-1);
    }
    printf("%lld",ans);
}

转载于:https://www.cnblogs.com/sweetphoenix/p/10793176.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值