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
n−1行,每行
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
1−u节点的键值;而对于求子树贡献是,用欧拉序就好了,进入子树前标记
−
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;
}