bzoj4765: 普通计算姬

链接

  http://www.lydsy.com/JudgeOnline/problem.php?id=4765

填坑

  今日七道题。(4/7)

题解

  套路大宗题目。
  首先肯定先dfs序,记录in和out,这样就转成单点修改区间查询,显然树状数组。
  那么如果求一个 sumi 的值,直接用树状数组减一下就出来了。它要你求 sumi ,似乎没啥特殊性质,那就直接分块吧。
  一开始直接预处理出 sumi 的每一块的和,查询的时候整块的直接加进答案,散的可以用树状数组。
  那么考虑修改对所维护信息的影响,树状数组上,直接单点修改就好了。
  块上,显然一个块内有多少点是这个点的祖先(包括自己),答案就要加上几乘(v-last[v])。
  那么,就要维护f[i][j]表示i这个块内,有多少点是j的祖先(包含j自身)。这个可以dfs求出,加一个桶,经过一个点就把那个点所在的快放进桶。对每个点扫描所有的桶,就能够求出f了。
  查询的复杂度: O(QNlogN)
  修改的复杂度: O(QN)
  其中 Q 是和N同级的,而 O(NNlogN) 是很大的,在 N=105 时值为 108.7 ,慢的不得了。(但可以卡过)
  考虑优化,可以修改块的大小来平衡这个复杂度。
  设块的大小为 x
  修改:O(Nx+logN)O(Nx)
  查询: O(Nx+xlogN)O(xlogN)
  那就是要平衡 Nx+xlogN
  均值不等式就发现 x=NlogN 时时间复杂度最低。
  这样复杂度是 O(NNlogN) 好像没卵用
  分析了这么半天,实际评测发现块大小改不改时间都一样哎╮(╯_╰)╭

代码

//分块+dfs序+树状数组
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 100010
#define maxs 320
#define lowbit(x) (x&-x)
#define ll unsigned long long
using namespace std;
ll ta[maxn], s[maxs], now[maxn], tid[maxn], out[maxn], tong[maxs], head[maxn], tot,
    root, N, M, size, to[maxn*2], nex[maxn*2];
int f[maxs][maxn];
inline void adde(ll a, ll b){to[++tot]=b;nex[tot]=head[a];head[a]=tot;}
inline ll read(ll x=0)
{
    char c=getchar();
    while(c<48 or c>57)c=getchar();
    while(c>=48 and c<=57)x=(x<<1)+(x<<3)+c-48,c=getchar();
    return x;
}
void dfs(ll pos, ll pre)
{
    ll p, i;
    tid[pos]=++tid[0];
    tong[pos/size]++;
    for(i=1;i<=N/size;i++)f[i][pos]+=tong[i];
    for(p=head[pos];p;p=nex[p])if(to[p]^pre)dfs(to[p],pos);
    tong[pos/size]--;
    out[pos]=tid[0];
}
inline void add(ll pos, ll v){for(;pos<=N;pos+=lowbit(pos))ta[pos]+=v;}
inline ll sum(ll pos)
{
    ll ans=0;
    for(;pos;pos-=lowbit(pos))ans+=ta[pos];
    return ans;
}
inline ll subtree_sum(ll pos){return sum(out[pos])-sum(tid[pos]-1);}
void init()
{
    ll i, a, b, l=log2(N);
    N=read(), M=read();
    size=l?sqrt(N):sqrt(N/l);
    for(i=1;i<=N;i++)now[i]=read();
    for(i=1;i<=N;i++)
    {
        a=read(),b=read();
        if(!a)root=b;else adde(a,b),adde(b,a);
    }
    dfs(root,-1);
    for(i=1;i<=N;i++)add(tid[i],now[i]);
    for(i=1;i<=N;i++)s[i/size]+=subtree_sum(i);
}
inline void chg(ll u, ll v)
{
    ll d=v-now[u], i;
    add(tid[u],d);
    for(i=1;i<=N/size;i++)s[i]+=f[i][u]*d;
    now[u]=v;
}
inline void quiry(ll l, ll r)
{
    ll i; ll ans=0;
    for(i=l/size+1;i<r/size;i++)ans+=s[i];
    for(i=l;i/size==l/size and i<=r;i++)ans+=subtree_sum(i);
    if(l/size^r/size)for(i=r;i/size==r/size and i>=l;i--)ans+=subtree_sum(i);
    printf("%llu\n",ans);
}
void work()
{
    ll x, y, type;
    while(M--)
    {
        type=read(), x=read(), y=read();
        if(type==1)chg(x,y);
        else quiry(x,y);
    }
}
int main()
{
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值