牛客挑战赛32 E树上逆序对(贡献思维+dp+欧拉序+BIT)

E 树上逆序对

题意:

输入 n ( 1 e 5 ) n(1e5) n(1e5),第二行输入 n n n个数 a i ( 1 e 9 ) a_i(1e9) ai(1e9)表示键值( a i a_i ai各不相同),接下来输入 n − 1 n-1 n1行,每行 u , v u,v u,v是树边。
对于每个节点可以键值取反变成 − a i -a_i ai.树上逆序对定义——祖先节点的键值大于儿子节点的键值,再输入 q ( 1 e 5 ) q(1e5) q(1e5),表示有 q q q组询问,每个询问输入 x ( 3 e 4 ) x(3e4) x(3e4),问是否可以通过取反操作把树变成逆序对为 x x x的树。

题解:

可以只考虑比较大的数对答案的贡献,对于一个数来说,如果取正,那么子树中键值比它小的就是贡献,取负,祖先节点比它小的就是贡献。取正,取负对应不同的贡献,那么就是一个 d p dp dp问题了,注意用 b i t s e t bitset bitset优化。
如何求子树中比它键值小的点个数和祖先比它键值小的个数?
一种方法是无脑树剖+主席树(码量有点大)
另一种就是欧拉序+BIT,对于求祖先节点贡献时候,可以在 d f s dfs dfs的时候就把它求出来,刚进入子树时把键值插入BIT,出子树是在BIT中删除键值,这样每次 d f s dfs dfs,BIT中都是 1 − u 1-u 1u节点的键值;而对于求子树贡献是,用欧拉序就好了,进入子树前标记 − u -u u,进入子树后标记 u u u,求出这颗树的欧拉序后,按欧拉序扫一遍,遇到 − u -u u就减去小于 a u a_u au的贡献,遇到 u u u就加上小于 a u a_u au的贡献。这种求法也算是一种套路吧。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
struct Edge{int v,nxt;}e[N<<1];
int head[N],cnt;
int n,a[N],x[N],nx;
bitset<30009>dp;
inline int getid(int y){return lower_bound(x+1,x+nx+1,y)-x;}
inline void add(int u,int v){
    e[cnt]=(Edge){v,head[u]};
    head[u]=cnt++;
}
int h[N];
void update(int x,int y){for(;x<N;x+=(x&-x))h[x]+=y;}
int query(int x){int ans=0;while(x)ans+=h[x],x-=(x&-x);return ans;}
int f[N],g[N];
int dfn[N],rk[N*2],num;
void dfs(int u,int fa){
    dfn[u]=++num;rk[num]=-u;
    f[u]=query(a[u]);
    update(a[u],1);
    for(int i=head[u],v;~i;i=e[i].nxt){
        v=e[i].v;
        if(v==fa)continue;
        dfs(v,u);
    }
    update(a[u],-1);
    rk[++num]=u;
}
int main(){
   // freopen("tt.in","r",stdin),freopen("tt.out","w",stdout);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i],x[i]=a[i];nx=n;
    sort(x+1,x+n+1);nx=unique(x+1,x+n+1)-x-1;
    for(int i=1;i<=n;i++)a[i]=getid(a[i]);
    memset(head,-1,sizeof(head));
    for(int i=1,u,v;i<n;i++)cin>>u>>v,add(u,v),add(v,u);
    dfs(1,0);
    for(int i=1;i<=num;i++){
        int u=abs(rk[i]);
        if(rk[u]<0)update(a[u],1);
        if(rk[i]==-u)g[u]-=query(a[u]-1);
        else g[u]+=query(a[u]-1);
    }
    dp[0]=1;
    for(int i=1;i<=n;i++)dp=(dp<<f[i])|(dp<<g[i]);
    int q,t;
    cin>>q;
    while(q--){
        cin>>t;
        if(dp[t])puts("Orz");else puts("QAQ");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值