这道题要求某区间内比h小的个数,其实这里可以类似于树状数组求逆序数那样。关键是如何转换成树状数组的模型,这才是本题的难点。
我们首先分析,如果知道h在该区间的哪个位置,那么剩下的就很好做了。我们还可以发现,如果找到了当前的比h小的所有点(大于的点我们先忽略掉),那么我们就可以用树状数组求它的[l,r]区间的和。这样就跟树状数组有了一点联系,但是还不够,因为我们发现,h的大小会影响我们所要找的区间。什么意思呢?就是我们先找到的是h1,再找h2,但h1>h2,就会出现这样的问题:h1所对应的区间找到了,但再找h2时,它对应的区间中可能会有比h2大的数,这样就不符合条件了。所以说这里有一个离线算法,就是先把所有的询问存下来,再按照h的大小进行从小到大排序,然后根据h的大小,从小到大地一个个插入所有区间内的数,这样就符合条件了。
#include
#include
#include
#include
using namespace std;
const int maxn = 1e5+5;
struct Query
{
int l,r,h;
int id;
bool operator < (Query a)
{
return h < a.h;
}
}q[maxn];
struct Node
{
int num;
int id;
bool operator < (Node a)
{
return num < a.num;
}
}a[maxn];
int n,m;
int tree[maxn<<2],ans[maxn];
int lowbit(int x)
{
return x & (-x);
}
void update(int x,int d)
{
for(int i = x; i <= n; i += lowbit(i))
tree[i] += d;
}
int getsum(int x)
{
int sum = 0;
for(int i = x; i > 0; i -= lowbit(i))
sum += tree[i];
return sum;
}
int main()
{
int t,cas = 1;
cin >> t;
while(t--)
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> a[i].num;
a[i].id = i;
}
for(int i = 1; i <= m; i++)
{
cin >> q[i].l >> q[i].r >> q[i].h;
q[i].l++, q[i].r++;
q[i].id = i;
}
sort(a+1,a+1+n);
sort(q+1,q+1+m);
memset(tree,0,sizeof(tree));
int idx = 1;
for(int i = 1; i <= m; i++)
{
while(q[i].h >= a[idx].num && idx <= n)
{
update(a[idx].id,1);
idx++;
}
ans[q[i].id] = getsum(q[i].r) - getsum(q[i].l-1);
}
printf("Case %d:\n",cas++);
for(int i = 1; i <= m; i++)
printf("%d\n",ans[i]);
}
return 0;
}
枚举C点,那么[1 , c-1]区间内,满足i
#include
#include
#include
#include
#include
const int N=1e5+100;
using namespace std;
typedef long long ll;
int c[N],n; //树状数组
int f[N],g[N];
int a[N];
int lowbit(int x)
{
return x&-x;
}
void update(int x)
{
while(x<=n)
{
c[x]+=1;
x+=lowbit(x);
}
}
int getsum(int x)
{
int ans=0;
while(x>0)
{
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)
{
f[i]=getsum(a[i]);
update(a[i]);
}
memset(c,0,sizeof(c));
for(int i=n;i>=1;i--)
{
update(a[i]);
g[i]=n-i+1-getsum(a[i]);
}
ll ans=0,sum=0;
for(int i=1;i<=n;i++)
{
ans+=sum*g[i];
sum+=f[i];
}
printf("%I64d\n",ans);
}
return 0;
}
涉及历史查询,一眼主席树的题目,关键的如何进行区间修改。
主席树与线段树不同之处就是 每一棵树是不能修改的,也就是不能够pushDown。那么能否一直新建节点?答案是不可以,如果用新建节点来替代pushDown操作,那么每一次都会一直新建到叶子节点,空间肯定是撑不住的。
主席树区间修一个很巧妙的方法:更新的时候和普通线段树一样,pushup 时由于没有pushDown 子树是不会更新的,所以用自己的 lazy 更新就可以了.也就是 sum[i] = sum[lson[i]] + sum[rson[i]] + lazy[i] * (r - l + 1)。查询的时候,将每一个节点的 lazy 值放在函数参数上带着,解决了pushDown的问题。
#include
#define debug(x) cout << "[" << #x <<": " << (x) <<"]"<< endl
#define pii pair
#define clr(a,b) memset((a),b,sizeof(a))
#define rep(i,a,b) for(int i = a;i < b;i ++)
#define pb push_back
#define MP make_pair
#define LL long long
#define ull unsigned LL
#define ls i << 1
#define rs (i << 1) + 1
#define fi first
#define se second
#define ptch putchar
#define CLR(a) while(!(a).empty()) a.pop()
using namespace std;
inline LL read() {
LL s = 0,w = 1;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') w = -1;
ch = getchar();
}
while(isdigit(ch))
s = s * 10 + ch - '0',ch = getchar();
return s * w;
}
inline void write(LL x) {
if(x < 0)
putchar('-'), x = -x;
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int maxn = 1e5 + 10;
const int M = maxn * 40;
LL C[M],lazy[M];
int lson[M],rson[M];
int tot,T[maxn];
void build(int &i,int l,int r){
i = ++ tot;
C[i] = lazy[i] = 0;
if(l == r){
LL x = read();
C[i] = x;
return ;
}
int mid = (l + r) >> 1;
build(lson[i],l,mid);
build(rson[i],mid + 1,r);
C[i] = C[lson[i]] + C[rson[i]];
}
void update(int l,int r,int ul,int ur,int &i,int last,LL val){
C[++ tot] = C[last];
lson[tot] = lson[last];
rson[tot] = rson[last];
lazy[tot] = lazy[last];
i = tot;
if(ul <= l && r <= ur){
C[i] += (r - l + 1) * val;
lazy[i] += val;
return ;
}
int mid = (l + r) >> 1;
if(ul <= mid) update(l,mid,ul,ur,lson[i],lson[last],val);
if(ur > mid) update(mid + 1,r,ul,ur,rson[i],rson[last],val);
C[i] = C[lson[i]] + C[rson[i]] + (r - l + 1) * lazy[i];
}
LL query(int l,int r,int ql,int qr,int i,LL add){
if(ql <= l && r <= qr){
return C[i] + add * (r - l + 1);
}
int mid = (l + r) >> 1;
LL ans = 0;
if(ql <= mid) ans += query(l,mid,ql,qr,lson[i],add + lazy[i]);
if(qr > mid) ans += query(mid + 1,r,ql,qr,rson[i],add + lazy[i]);
return ans;
}
int main() {
int n = read(),m = read();
build(T[0],1,n);
int now = 0;
while(m --){
char op; scanf(" %c",&op);
if(op == 'C'){
int l = read(),r = read(),d = read();
update(1,n,l,r,T[now + 1],T[now],d);
++ now;
}
else if(op == 'Q'){
int l = read(),r = read();
write(query(1,n,l,r,T[now],lazy[now])); ptch('\n');
}
else if(op == 'H'){
int l = read(),r = read(),t = read();
write(query(1,n,l,r,T[t],lazy[t])); ptch('\n');
}
else {
int t = read();
now = t;
}
}
return 0;
}
这题其实是分两个题来做
对于R > 1时,设计状态如下:
fi,j,k:从(1,1)到(i,j)的矩形里,大于等于k的数字的总和
gi,j,k:从(1,1)到(i,j)的矩形里,大于等于k的数字的个数
每次询问等价成4个矩形里的结果相加减,二分k,暴力算最小的那个数要多少个就行
对于R == 1时,建立一棵主席树就行了
#include
#include
#include
#include
#include
#include
#include
#include
动态维护树上两点间第 k 值 LCT 不能直接维护第 k 值,也很难嵌套其他数据结构,所以我们不考虑它;发现只有 加边没有删边,考虑使用主席树启发式合并 具体来说, 加边时, 把节点少的主席树合并到节点多的主席树上面,复杂度 slogs,s 为 小主席树的大小, 同时动态更新父节点的倍增数组,复杂度也是 slogs 的 查询时, 进入两个点以及它们的 lca 和 lca 的爹的主席树中上上下下加加减减
#include
#include
#include
#define ls ch[now][0]
#define rs ch[now][1]
#define ols ch[las][0]
#define ors ch[las][1]
const int N=3e5+10;
int ch[N*
30][2],sum[N*
30],tot;
int f[N][20],dep[N],n,m,n_,t,tmp,siz[N],root[N],a[N],b[N],testcase;
void updata(int now){sum[now]=sum[ls]+sum[rs];}
int rebuild(int las,int l,int r,int pos)
{
int now=++tot;
if(l==r) {sum[now]+=sum[las]+1;return now;}
int mid=l+r>>1;
if(pos<=mid)
{
ls=rebuild(ols,l,mid,pos);
rs=ors;
}
else
{
ls=ols;
rs=rebuild(ors,mid+1,r,pos);
}
updata(now);
return now;
}
int head[N],to[N<<1],Next[N<<1],cnt;
void add(int u,int v)
{
to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int dfs(int now,int fa)
12
{
int s=1;
root[now]=rebuild(root[fa],1,n,b[now]);
f[now][0]=fa;
dep[now]=dep[fa]+1;
for(int i=1;i<=18;i++)
f[now][i]=f[f[now][i−1]][i−1];
for(int i=head[now];i;i=Next[i])
if(to[i]!=fa)
s+=dfs(to[i],now);
return s;
}
void swap(int &x,int &y){tmp=x,x=y,y=tmp;}
int rt(int now)
{
for(int i=18;~i;i−−)
if(f[now][i])
now=f[now][i];
return now;
}
void Merge(int x,int y)
{
int rx=rt(x),ry=rt(y);
if(siz[rx]=dep[y])
x=f[x][i];
if(x==y) return x;
for(int i=18;~i;i−−)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
int query(int rt1,int rt2,int rt3,int rt4,int l,int r,int k)
{
if(l==r) return a[l];
int mid=l+r>>1,s=sum[ch[rt1][0]]+sum[ch[rt2][0]]−sum[ch[rt3][0]]−sum[ch[rt4
][0]];
if(s>=k) return query(ch[rt1][0],ch[rt2][0],ch[rt3][0],ch[rt4][0],l,mid,k);
else return query(ch[rt1][1],ch[rt2][1],ch[rt3][1],ch[rt4][1],mid+1,r,k−s);
}
int main()
{
scanf("%d",&testcase);
tot=0,cnt=0;
scanf("%d%d%d",&n_,&m,&t);
for(int i=1;i<=n_;i++) scanf("%d",a+i),b[i]=a[i];
std::sort(a+1,a+1+n_);
n=std::unique(a+1,a+1+n_)−a−1;
for(int i=1;i<=n_;i++) b[i]=std::lower_bound(a+1,a+1+n,b[i])−a;
for(int u,v,i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n_;i++)
if(!f[i][0])
siz[i]=dfs(i,0);
int lastans=0;char op[4];
for(int u,v,k,i=1;i<=t;i++)
{
scanf("%s%d%d",op,&u,&v);
u^=lastans,v^=lastans;
if(op[0]=='Q')
{
scanf("%d",&k);
k^=lastans;int lca=LCA(u,v);
printf("%d\n",lastans=query(root[u],root[v],root[lca],root[f[lca][0]],1,n,
k));
}
else
Merge(u,v);
}
return 0;
}