BZOJ 3653 谈笑风生

82 篇文章 0 订阅
19 篇文章 0 订阅

Problem

BZOJ
然而是权限题,如果你像蒟蒻我一样没有权限号请上洛谷~

Solution

据说正解是主席树???
不管了,反正主席树常数巨大,线段树虽然暴力好歹也能卡过去,而且这题数据还比较水。
其实是我不会打主席树
法一
显然,分两种情况,若b是a的祖先,那么贡献就是sz[a]-1,而若b在a的子树中,则贡献为sz[b]-1。
第一种情况很好解决,考虑第二种情况。
我们将树按照dfs序拍扁成数组,然后在数组上根据dfs序中答案的最小最大深度,进行dfs搜索。注意要满足depth<=mx[rt]的限制条件才能返回区间的值,否则可能会多计算了某些深度之差大于k的值。另外要用mn[rt]来剪枝,也就是说如果区间中最小的深度都已经大于了depth,显然就不需要继续向下搜了。

然而这可以被数据卡掉,所以就介绍不会被卡的法二……
法二
Orz了一发boshi的题解
同样也是利用了dfs序,大致思想就是将询问离线,然后树形dp:维护一个序列,a[i]表示深度为i的节点的sz之和,那么答案显然就是sum[a[i]+k]-sum[a[i]],考虑用树状数组维护,退出该节点时插入此节点的贡献即可。答案不能受非子树,深度符合要求的贡献影响,在进入节点时减去其贡献即可。
时间复杂度 O(nlogn) O ( n l o g n )

Code

#include <cstdio>
using namespace std;
typedef long long ll;
template <typename Tp> inline void read(Tp &x)
{
    x=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
const int maxn=300010;
struct data{int v,nxt;}edge[maxn<<1];
int n,m,p,dfc,sz[maxn],deep[maxn],dfn[maxn][2],head[maxn];
int xu[maxn<<1],a[maxn<<1],mx[maxn<<3],mn[maxn<<3];
ll ans,sum[maxn<<3];
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
inline void insert(int u,int v)
{
    edge[++p]=(data){v,head[u]};head[u]=p;
    edge[++p]=(data){u,head[v]};head[v]=p;
}
void dfs(int x,int pre)
{
    dfn[x][0]=++dfc;sz[x]=1;
    for(int i=head[x];i;i=edge[i].nxt)
      if(edge[i].v!=pre)
      {
        deep[edge[i].v]=deep[x]+1;
        dfs(edge[i].v,x);
        sz[x]+=sz[edge[i].v];
      }
    dfn[x][1]=++dfc;
    xu[dfn[x][0]]=xu[dfn[x][1]]=deep[x];
    a[dfn[x][0]]=sz[x]-1;a[dfn[x][1]]=0;
}
void build(int l,int r,int rt)
{
    if(l==r){mx[rt]=mn[rt]=xu[l];sum[rt]=a[l];return ;}
    int m=(l+r)>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    mx[rt]=max(mx[rt<<1],mx[rt<<1|1]);
    mn[rt]=min(mn[rt<<1],mn[rt<<1|1]);
}
ll query(int l,int r,int L,int R,int now,int depth)
{
    if(mn[now]>depth) return 0;
    if(L<=l&&r<=R&&mx[now]<=depth) return sum[now];
    int m=(l+r)>>1;ll res=0;
    if(L<=m) res+=query(l,m,L,R,now<<1,depth);
    if(m<R) res+=query(m+1,r,L,R,now<<1|1,depth);
    return res;
}
int main()
{
    read(n);read(m);
    for(int i=1,x,y;i<n;i++) {read(x);read(y);insert(x,y);}
    deep[1]=1;dfs(1,0);
    build(1,dfc,1);
    for(int i=1,x,y;i<=m;i++)
    {
        read(x),read(y);
        ans=(ll)min(deep[x]-1,y)*(sz[x]-1);
        ans+=query(1,dfc,dfn[x][0]+1,dfn[x][1],1,deep[x]+y);
        printf("%lld\n",ans);
    }
    return 0;
}

boshi’s code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>

#define MX 300003

using namespace std;

typedef long long ll;

int fst[MX],nxt[MX*2],v[MX*2],siz[MX],dep[MX],lnum;
ll sum[MX];
vector<int>qk[MX],qid[MX];
ll ans[MX];
int n,m;

void add(int p,ll x){for(;p<MX;p+=(p&(-p)))sum[p]+=x;}
ll qsm(int p){ll x=0;for(;p;p-=(p&(-p)))x+=sum[p];return x;}
void addeg(int nu,int nv)
{
    nxt[++lnum]=fst[nu];
    fst[nu]=lnum;
    v[lnum]=nv;
}
void dfs(int x,int fa,int d)
{
    siz[x]=1,dep[x]=d;
    for(int i=fst[x];i!=-1;i=nxt[i])
        if(v[i]!=fa)
            dfs(v[i],x,d+1),
            siz[x]+=siz[v[i]];
}
void calc(int x,int fa)
{
    for(int i=0;i<qk[x].size();i++)ans[qid[x][i]]-=(qsm(min(dep[x]+qk[x][i],MX-1))-qsm(dep[x]));
    for(int i=fst[x];i!=-1;i=nxt[i])
        if(v[i]!=fa)
            calc(v[i],x);
    for(int i=0;i<qk[x].size();i++)ans[qid[x][i]]+=(qsm(min(dep[x]+qk[x][i],MX-1))-qsm(dep[x])+(ll)min(dep[x]-1,qk[x][i])*(ll)(siz[x]-1));
    add(dep[x],siz[x]-1);
}
void input()
{
    int a,b;
    memset(fst,0xff,sizeof(fst)),lnum=-1;
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        addeg(a,b);
        addeg(b,a);
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        qk[a].push_back(b);
        qid[a].push_back(i);
    }
}
int main()
{
    input();
    dfs(1,0,1);
    calc(1,0);
    for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值