【JZOJ5699】【GDOI2018 day1】涛涛接苹果(appletree)(三维偏序问题)

Problem

  给定一颗n个节点的树,初始时第i个节点的苹果重量为ai。涛涛每天早上会摘掉根上的苹果;下午,所有苹果下降一层(即从子节点掉到父亲节点)。魔法师洋洋会进行m次单点修改。父亲图图提出了q个问题,每个问题是:求在第t天早上(涛涛采摘前),以x为根的子树的重量和。

Hint

这里写图片描述

Solution

10~40points:模拟+线段树/树状数组+分层求前缀和

  这些方法的话,在我的GDOI2018事故记录中也有,这里便不作赘述。
  时间复杂度: O ( m a x { t } n ) (模拟)/ O ( ( m + q ) l o g 2 n ) (线段树/树状数组)/ O ( n + m + q )

Code

  我并没有拿到程序啦╮(╯▽╰)╭而且我也懒得再打一遍。

60points:二维偏序问题

  其实我在比赛时大概想到了他询问的是所有满足某个条件的点的重量和,但是我当时的混沌思想受到x=1的特殊情况的影响,傻逼逼地觉得应该按照bfs序重标号,然后就感觉非常难做。
  其实,一看到是维护子树信息,我就应当矢志不渝地执着于dfs序的!

  言归正传,先考虑没有修改的情况。
  设在第t天询问点x,若点y对答案造成贡献,当且仅当P[x]≤dfn[y]≤Q[x](y在x子树内)且deep[y]≥deep[x]+t-1(点x上的苹果没有掉出x的子树内)。
  这样,原问题就被转化为了一个二维偏序问题。什么是二维偏序问题呢?就是问你满足xi<某个数,yi<某个数的点i的个数。这其实相当于在一个二维平面上有许多个点,求一个矩阵覆盖了多少个点。
  回到原问题上,我们可将deep[y]作为横坐标,dfn[y]作为纵坐标,然后把每个点以及询问(询问的deep[x]要+t-1)插到这个二维平面里。
  比较简单的解法有扫描线+树状数组。也即你先按照deep从大到小排序(当deep相同时,点优先,询问放在后面),然后有点就插点,有询问则区间求和。
  当然,如果dalao想拿这题练练手的话,也是可以打个主席树、KD树啊什么的(我也准备拿这题打个主席树练练手当然我不是dalao)。
  时间复杂度: O ( ( n + q ) l o g 2 ( n + q ) )

Code

  反正下面的100points做法也有,故不单独贴了。

100points:三维偏序问题

  我们发现这道题还有修改,那么60points做法便吃不消了。
  其实,我们可以考虑对时间进行CDQ分治。我以前做过某道CDQ分治例题,所以尽管这么蒟还是会滴。(不懂的童鞋戳这里
  这题也是一样。我们先将所有操作(一开始的n个点、m个加点、q个询问)整到一起,设cnt=n+m+q,则将这cnt个操作按时间排序(注意,一开始的n个点的时间设为0,可以不必排序它们,只排序加点和询问),然后开始CDQ分治:
  对于区间[l,r],记l,r中点为mid,则整块区间被划分为左、右两区间;记录左区间所有的点、加点,设共有ins个;记录右区间所有的询问,设共有que;把ins+que这些个操作整到一起,进行一遍60points的矩阵覆盖。
  然后,我们在继续递归分治的时候,可以考虑进行一个小优化:若ins=0(左区间全为询问)或ins=mid-l+1(左区间无询问),则不必递归左区间;右区间同理。详见Code。
  
  时间复杂度的话,估计许多蒟蒻们是不会分析的,我来帮分析一下:首先,不断二分分治,这总共有log层;每一层的总长度为cnt;那么,每一层的时间复杂度是多少呢?
  首先,我们知道,每一层是可能有 O ( c n t l o g 2 c n t ) 的时间复杂度的,因为如果这一层是[1,cnt]这一整块区间,时间复杂度就是如此;但这一层更可能是下图所示的第2~4行:
这里写图片描述
  如图,当cnt=16时,递归会进行 l o g 2 16 5 层。第2~4层的递归,区间并非一整块[1,cnt]。
  在同一层内,对于每一块区间,我们都要花 O ( n l o g 2 n ) (n为区间长度)的时间去进行矩阵覆盖。以第2层为例,时间复杂度为 O ( 8 l o g 2 8 + 8 l o g 2 8 ) = O ( 16 l o g 2 8 ) < O ( 16 l o g 2 16 ) 。因此,每一层的时间复杂度均 O ( c n t l o g 2 c n t ) 。于是:
  时间复杂度: O ( c n t ( l o g 2 c n t ) 2 )

Code

  一开始我把文件输出打错了,打成了freopen("appletree.out","r",stdin);了,一直爆零,害得我调了好久o(╥﹏╥)o
  后来,我使用的c++自带快排不知为何TLE了,于是我手打了快排。。。(仿若回到了初涉c++不会使用sort函数的蒟蒻时光)
  然后,我手打的快排又不知为何RE了(其实我怀疑了好半天是不是dfs求dfn和deep的时候爆栈了,没想到是快排RE了),于是我改成了堆排序。。。(一把辛酸泪o(╥﹏╥)o
  但是故事还没结束,我的堆排序直接套用了c++STL表中的set,然后又莫名WA了——我发现它有时会错乱一下,修改到其他数组的值——我又检查了一遍数组大小,开够了啊!!!(•́へ•́╬)(╯‵□′)╯︵┻━┻
  于是,我又手打了堆。。。(如丧考妣
  所以,下面代码中含有手打堆,故看起来会比较长:

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+1,M=N<<1;
int i,n,m,q,a[N],x,y,tot,tov[M],next[M],last[N],cnt,time,dfn[N],P[N],Q[N],deep[N];
struct oper
{
    bool p;int t,x,w,num;
    oper(){}
    oper(bool _p,int _t,int _x,int _w,int _num){p=_p;t=_t;x=_x;w=_w;num=_num;}
    bool operator>(const oper&a)const//我重载的是按照deep的大于号
    {
        int ac=deep[a.x]+a.t-a.p,c=deep[x]+t-p;
        return c>ac||c==ac&&!p; 
    }
}o[M+N];
void read(int &x)
{
    char ch=getchar(); x=0;
    for(;!isdigit(ch);ch=getchar());
    for(;isdigit(ch);x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
}
inline void link(int x,int y)
{
    tov[++tot]=y;
    next[tot]=last[x];
    last[x]=tot;
}
bool p;int t,w;
void scan()
{
    read(n);read(m);read(q);
    fo(i,1,n)
    read(a[i]),o[i]=oper(0,0,i,a[i],0);
    fo(i,1,n-1)
    {
        read(x);read(y);
        link(x,y);link(y,x);
    }
    fo(i,1,m)
    read(t),read(x),read(w),o[n+i]=oper(0,t,x,w,0);
    fo(i,1,q)
    read(t),read(x),o[n+m+i]=oper(1,t,x,0,i);
}

void dfs(int x,int f)
{
    dfn[x]=P[x]=++time;
    int i,y;
    for(i=last[x];i;i=next[i])
        if((y=tov[i])!=f)
        {
            deep[y]=deep[x]+1;
            dfs(y,x);
        }
    Q[x]=time;
}
//看似爆栈了的dfs
oper sta[M+N],b[M+N]; 
inline bool cmp(oper a,oper b,bool t){return t?a.t<b.t||a.t==b.t&&a.p:a>b;}
void up(int v,bool t)
{
    if(v==1)return;
    int f=v>>1;
    if(cmp(sta[v],sta[f],t))swap(sta[v],sta[f]),up(f,t);
}
void insert(oper a,bool t)
{
    sta[++cnt]=a;
    up(cnt,t);
}
void down(int v,bool t)
{
    int A=v<<1,B=A+1;bool pa=A<=cnt&&sta[A].t>=0,pb=B<=cnt&&sta[B].t>=0; 
    if(!pa&&!pb){sta[v].t=-1;return;}
    int x=(!pb||pa&&cmp(sta[A],sta[B],t)?A:B);
    sta[v]=sta[x],down(x,t); 
}
//stack_sort,当中所有函数的参数t=1时以时间为关键字,t=0时以deep为关键字

ll c[N],ans[N];
inline int lowbit(int x){return x&-x;}
void add(int x,ll val)
{
    for(;x<=n;x+=lowbit(x))
    c[x]+=val;
}
ll query(int x)
{
    ll ans=0;
    for(;x;x-=lowbit(x))ans+=c[x];
    return ans;
}
ll query(int x,int y){return query(y)-query(x-1);}
//树状数组
void work()
{
    fo(i,1,cnt)
    {
        b[i]=sta[1];down(1,0);
        if(!b[i].p)
                add(dfn[b[i].x],b[i].w);
        else    ans[b[i].num]+=query(P[b[i].x],Q[b[i].x]);
    } 
    fo(i,1,cnt)if(!b[i].p)add(dfn[b[i].x],-b[i].w);
}
//矩阵覆盖
void CDQpartition(int l,int r)
{
    if(l==r)return; 
    int mid=l+r>>1,ins=0,que=0;cnt=0;

    fo(i,l,mid)if(!o[i].p)++ins,insert(o[i],0);
    fo(i,i,r)  if( o[i].p)++que,insert(o[i],0);

    if(ins&&que)work();

    if(ins&&ins<mid-l+1)CDQpartition(l,mid);
    if(que&&que<r-mid)CDQpartition(mid+1,r);
}
//CDQ分治
void stasort(int l,int r)
{
    cnt=0;
    fo(i,l,r)insert(o[i],1);
    fo(i,l,r)o[i]=sta[1],down(1,1);
}
//按时间进行堆排序
void print()
{
    fo(i,1,q)printf("%lld\n",ans[i]);   
}

int main()
{
    freopen("appletree.in","r",stdin);
    freopen("appletree.out","w",stdout);
    scan();
    dfs(1,0);
    stasort(n+1,n+m+q);
    CDQpartition(1,n+m+q);
    print();
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/80214998