洛谷上若干经典树状数组题解集(lg1774、lg1966、lg1972)

juejin传送门

lg1774

传送门

经典题:排序只能交换相邻元素的最少操作次数。

为什么答案是逆序数:升序数组逆序数为0。一次操作逆序数减少0或1。因此最优方案必定满足:每一步的逆序数都能减少1。证毕。

但我们还是不知道最优方案长啥样!对于a[i] <= a[i+1]i,交换它们是多余的,因为逆序数不减少。因此我们设右侧是已经升序排好的部分,那么我们就需要在左侧找到最大值并把它放到期望的位置(当然还有一些副作用的)。于是我们发现这就是冒泡排序的过程!

这里我们又得到一个经典结论:冒泡排序的交换次数等于逆序数。

lg1966

传送门

排序不等式+lg1774的升级版。

优化函数=sum(a[i]^2)+sum(b[i]^2)-2*sum(a[i]*b[i]),忽略常量,所以需要最大化sum(a[i]*b[i])。套用排序不等式的结论“正序和最大”即可。

洛谷上面有许多题解,但是把下一步讲清楚的题解真的几乎没有,这里我尝试填补这个空白。

lg1774的升级版

这样得到的问题是lg1774的一个拓展版。那么可以猜测一个拓展版的解答(xs,根本猜不到):

ia[x]a中排名x的元素的下标,即rka的反函数。ib[x]同理。

c[x]:和a[x]排名相同的b中元素在b数组的下标。和a[x]排名相同的b中元素目标是在x位置,所以目标就是c升序。

c的求法:rep (i, 1, n) c[i] = ib[rka[i]];

最后看对a的操作怎么映射到对c的操作:a[i]a[i+1]的交换操作对应:

`c[i]后` = 和`a[i+1]原`排名相同的`b`中元素在`b`数组的位置 = `c[i+1]原`。

c[i+1]后同理。所以a[i]a[i+1]的交换操作等价于c[i]c[i+1]的交换操作。

结论:对c的操作方案就是对a的操作方案,而b不动。

简化一下c数组的求法

枚举排名ia[ia[i]]b[ib[i]]的排名显然都是i,因此有:rep (i, 1, n) c[ia[i]] = ib[i];

代码

朴素版

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
#define lowbit(x) ((x) & (-(x)))

const int N = 1e5 + 5, mod = 1e8 - 3;

int n, a[N], b[N], rka[N], ia[N], ib[N], c[N];
int C[N];

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

bool cmpa (int x, int y) {
    return a[x] < a[y];
}

bool cmpb (int x, int y) {
    return b[x] < b[y];
}

void mdy (int x, int v) {
    for (; x <= n; x += lowbit (x) ) C[x] += v;
}

int qry (int x) {
    int ans = 0;
    for (; x; x -= lowbit (x) ) ans += C[x];
    return ans;
}

int main() {
    read (n);
    rep (i, 1, n) read (a[i]);
    rep (i, 1, n) read (b[i]);
    rep (i, 1, n) ia[i] = ib[i] = i;
    sort (ia + 1, ia + n + 1, cmpa);
    sort (ib + 1, ib + n + 1, cmpb);
    rep (i, 1, n) rka[ia[i]] = i;
    rep (i, 1, n) c[i] = ib[rka[i]];
    int ans = 0;
    dwn (i, n, 1) {
        ans = (ans + qry (c[i] - 1) ) % mod;
        mdy (c[i], 1);
    }
    printf ("%d\n", ans);
    return 0;
}

简化版

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
#define lowbit(x) ((x) & (-(x)))

const int N = 1e5 + 5, mod = 1e8 - 3;

int n, a[N], b[N], ia[N], ib[N], c[N];
int C[N];

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

bool cmpa (int x, int y) {
    return a[x] < a[y];
}

bool cmpb (int x, int y) {
    return b[x] < b[y];
}

void mdy (int x, int v) {
    for (; x <= n; x += lowbit (x) ) C[x] += v;
}

int qry (int x) {
    int ans = 0;
    for (; x; x -= lowbit (x) ) ans += C[x];
    return ans;
}

int main() {
    read (n);
    rep (i, 1, n) read (a[i]);
    rep (i, 1, n) read (b[i]);
    rep (i, 1, n) ia[i] = ib[i] = i;
    sort (ia + 1, ia + n + 1, cmpa);
    sort (ib + 1, ib + n + 1, cmpb);
    rep (i, 1, n) c[ia[i]] = ib[i];
    int ans = 0;
    dwn (i, n, 1) {
        ans = (ans + qry (c[i] - 1) ) % mod;
        mdy (c[i], 1);
    }
    printf ("%d\n", ans);
    return 0;
}

lg1972

莫队做法略。

关键词:pre数组

这里介绍的做法:离线+树状数组。

考虑固定r的一系列询问。我们把r右侧的数组忽略掉,并记最后一次出现的数字贡献为1,非最后一次出现的数贡献则为0(这就是pre数组的本质)。实现方式:

if (pre[a[u]]) mdy (pre[a[u]], -1);
mdy (u, 1);
pre[a[u]] = u;

所以最终做法:离线按r排序,r枚举(增大)过程中只需要单点修改,处理r相同的一系列询问即询问一系列区间和。处理这两个需求只需要树状数组。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
#define lowbit(x) ((x) & (-(x)))

const int N = 1e6 + 5;

int n, m, a[N], pre[N], ans[N];
int C[N];

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

struct Qry {
    int l, r, idx;
    bool operator < (const Qry &rhs) const {
        return r < rhs.r;
    }
} q[N];

void mdy (int x, int v) {
    for (; x <= n; x += lowbit (x) ) C[x] += v;
}

int qry (int x) {
    int ans = 0;
    for (; x; x -= lowbit (x) ) ans += C[x];
    return ans;
}

int main() {
    read (n);
    rep (i, 1, n) read (a[i]);
    read (m);
    rep (i, 1, m) {
        int l, r;
        read (l, r);
        q[i] = {l, r, i};
    }
    sort (q + 1, q + m + 1);
    int u = 1;
    rep (i, 1, m) {
        int r = q[i].r;
        while (u <= r) {
            if (pre[a[u]]) mdy (pre[a[u]], -1);
            mdy (u, 1);
            pre[a[u]] = u;
            ++u;
        }
        ans[q[i].idx] = qry (r) - qry (q[i].l - 1);
    }
    rep (i, 1, m) printf ("%d\n", ans[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值