codeforces 414C 分治思想运用

5 篇文章 0 订阅
5 篇文章 0 订阅
这个题很劲啊。搞了我一下午。。
大意是:给你一个 2n 长度的随意的数组,有 m 次查询,每次从左到右把数组依次分成长度为qi的块,将其 reverse ,问每次翻转之后逆序对数多少
比较难想的一道题。有些逆序对的结论很显然,设一段区间 seg 的逆序对数为 a 那么翻转这个区间之后的逆序对数为C2|seg|a当然不能有重复的数出现,如果有重复的数出现,还需要减去 C2vi 其中 vi 表示 i 在区间出现的次数好的有了这些结论。其实并没有什么卵用。。。递归的思路是显然的,我们仿照归并排序的思路,先把左右区间搞出来,然后在计算出左区间对右区间的贡献值。可以在n2n复杂度预处理出来。然后重点就是查询了。一开始我更新到每个节点,设置了懒惰元素。但是不是 tle 就是 wa
之所以说这个题很劲,就是需要很抽象的想法才能搞掉。我们想想对于每一层 i ,设最开始在0层,那么这层有2i个小区间。每个小区间都对答案有一个独立的贡献 v v表示这个小区间左半部分对其右半部分的贡献,我们把这一层的小区间贡献的和求出来为 dp1[i] ,每当我们翻转这一层的时候,每个小区间都翻转了,但是每个小区间都是独立的,翻转就是一起翻转,于是再直接(归并倒着排)处理处翻转的和 dp2[i] 每次翻转就是交换 dp1,dp2 。再想想,翻转第 i 层,只会影响更大的层数,而不会影响之前的层数(这是因为翻转每个小区间,但是每个小区间的相对位置没变,而较小的层是左区间对右区间的贡献,左右相对位置没变)。

然后每次查询暴力更新最多n层,在求和输出即可

//
//  Created by Running Photon
//  Copyright (c) 2015 Running Photon. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <sstream>
#include <set>
#include <vector>
#include <stack>
#define ALL(x) x.begin(), x.end()
#define INS(x) inserter(x, x,begin())
#define ll long long
#define CLR(x) memset(x, 0, sizeof x)
using namespace std;
const int inf = 2e9;
const int MOD = 1e9 + 7;
const int maxn = (1 << 20) + 10;
const int maxv = 1e3 + 10;
const double eps = 1e-9;

ll dp1[21], dp2[21];
int a[maxn], ta[maxn], tb[maxn];
int n, m, tot;
void build(int head, int tail, int deep) {
    if(deep == n) {
        dp1[deep] = dp2[deep] = 0;
        return;
    }
    int mid = head + tail >> 1;
    build(head, mid, deep+1);
    build(mid, tail, deep+1);
    int tota = 0, totb = 0;
    for(int i = head; i < mid; i++) {
        ta[tota++] = a[i];
    }
    for(int i = mid; i < tail; i++) {
        tb[totb++] = a[i];
    }
    ta[tota] = tb[totb] = inf;
    int la = 0, lb = 0;
    ll ret = 0, retv = 0;
    for(int i = head; i < tail; i++) {
        if(tb[lb] <= ta[la]) lb++;
        else {
            retv += totb - lb;
            la++;
        }
    }
    la = lb = 0;
    for(int i = head; i < tail; i++) {
        if(ta[la] <= tb[lb]) a[i] = ta[la++];
        else {
            a[i] = tb[lb++];
            ret += tota - la;
        }
    }
    dp1[deep] += ret;
    dp2[deep] += retv;
}
int main() {
#ifdef LOCAL
    freopen("C:\\Users\\Administrator\\Desktop\\in.txt", "r", stdin);
    freopen("C:\\Users\\Administrator\\Desktop\\out.txt","w",stdout);
#endif
//  ios_base::sync_with_stdio(0);
    scanf("%d", &n);
    tot = 1 << n;
    std::vector<int> xs;
    for(int i = 0; i < tot; i++) {
        scanf("%d", a + i);
    }
    build(0, tot, 0);
    scanf("%d", &m);
    while(m--) {
        int q;
        scanf("%d", &q);
        ll sum = 0;
        q = n - q;
        for(int i = q; i <= n; i++) swap(dp1[i], dp2[i]);
        for(int i = 0; i <= n; i++) sum += dp1[i];
        printf("%lld\n", sum);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值