poj 2104

整体二分解法

  调了 n 久!!!
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>

#define maxn 220000
#define inf 1000000000

using namespace std;
struct query
{
  int x, y, k, s;
}q[maxn<<1], q1[maxn<<1], q2[maxn<<1];

int a[maxn], ans[maxn];
int n, m;


struct BIT {
  long long bit[maxn];
    
  inline void init() {
    memset(bit, 0, sizeof(bit));
  }

  inline int lowbit(int x) {
    return x&-x;
  }

  inline int sum(int i) {
    int res = 0;
    while (i > 0) {
      res += bit[i];
      i -= lowbit(i);
    }
    return res;
  }
  
  inline void add(int i, int x) {
    while (i <= n) {
      bit[i] += x;
      i += lowbit(i);
    }
  }

}T;


void divide(int head, int tail, int l, int r) {
  int mid = (l + r) >> 1;
  int tmp = 0;

  if (head > tail)
    return;
  
  if ( l == r) {
    for (int i = head; i <= tail; i++)  {
      ans[q[i].s] = l;
    }
    return;
  }
  
  T.init();
  
  for (int i = 1; i <= n; i++){
    if (a[i] <= mid)
      T.add(i, 1);
  }
  
  int L1 = 0, L2 = 0;
  
  for (int i = head; i <= tail; i++) {
    tmp = T.sum(q[i].y) - T.sum(q[i].x-1) - q[i].k;
    if (tmp < 0) {
      q2[++L2] = q[i];
    }
    
    else {
      q1[++L1] = q[i];
    }
  }
  
  for (int i = 1; i <= L1; i++) {
    q[head+i-1] = q1[i];
  }

  for (int i = 1; i <= L2; i++) {
    q[head + L1-1+i] = q2[i];
  }

  divide(head, head+L1 -1, l, mid);
  divide(head+L1, tail, mid+1, r);
  return ;
}


int main()
{
    memset(q, 0, sizeof(q));
    scanf("%d%d", &n, &m);
    for (int i=1; i<=n; i++) {
      scanf("%d", &a[i]);
    }
    
    int x, y, k;
    for (int i=1; i<=m; i++) {
      scanf("
%d%d%d", &x, &y, &k);
      q[i].x = x; q[i].y = y;
      q[i].k = k; q[i].s = i;
    }

    
    //divide(1, m, 0, inf);
    divide(1, m, -inf, inf);
    
    for (int i = 1; i<=m; i++)
      printf("%lld
", ans[i]);
    return 0;
}


我的不成熟算法
之前自己写了个整体快排算法(自定义的名词~~,和快排有一定的相似性,但并非是个排序算法~~~),求单个区间的 Kth 大可以用快排。这种方法用在本题上肯定会 TLE !。

首先第一步计算每个 [L, R] 区间内的小于 mid 的数,这样就可以知道 mid 在 [L, R] 区间的排名。如果某个 [L, R] 区间有 求第 101 大的数,且已知在该区间有 100 个数大于 mid。我们将遍历该区间,保存第一个碰到的大于 mid 的数。后面的就是冒泡算法!
这个算法的缺点就是需要完全地遍历 [L, R] 区间!! 如果 [L, R]区间中的大于 mid 的数要远远小于 kth, 那就增大 mid 后,再比较!!至于其它的情况,聪明的你肯定是懂的。
因为 n 个query 的结果肯定是 分散在 [-n, n] 空间内,可以通过比较 mid 数的方式将 n 个query 按结果分区。最终肯定能得出答案 [-n, -n+100] 有 m1 个 query, 在 [-n+101, -n +200] 有 m2 个query ......。因为采用的是树状数组,可以很快地求出每个 [L, R] 区间内大于某个数的数!可以根据这个来遍历一次获取到相应的 kth num。
这里使用的二分思想和划分树的很像!

CDQ 分治

莫队

  • 感觉像是 DP 算法 + 分块算法的结合体, 因为莫队算法用存储中间结果用于下次计算(DP 中的存储中间结果进行状态转移).
    中间也碰到一次踩内存的问题, 而且我的算法需要 1735 ms,这个有点长啊
  • 莫队就是 “有条理的暴力”, 如果数据量再大量就需要莫队套莫队
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>


const int maxn = 1e5+10;
const int maxq = 1e4+5;
const int inf = 1e9+5;


using namespace std;

int ans[maxq];
int n, m, qbsize, vbsize; // bsize means bulk size 一开始没有区分 qbsize, vbsize 导致程序后面踩内存,都写到 sumv[344] 时,即将数据写到 b[0] 上
int uniq_n[maxn], sumv[340], b[maxn], uniq_c = 0; // 340 ~= sqrt(1e5+10)

struct Num {
  int val, pos, rank, bulk_rank;
  bool operator <(const Num &a)const {
    return val < a.val;
  }
} sorted_num[maxn], num[maxn]; // 这样比较占用内存,有没有其它的方法?求解?

struct Query {

  int l, r, k, s;
  int lbulk, rbulk; // 用于莫队排序
  bool operator <(const Query &a)const {
    return lbulk == a.lbulk? rbulk < a.rbulk: lbulk < a.lbulk;
    }
} q[maxq];


void init() {
     memset(b, 0, sizeof(b));
     memset(uniq_n, 0, sizeof(uniq_n));
     memset(sumv, 0, sizeof(sumv));
     memset(q, 0, sizeof(q));
     memset(ans, 0, sizeof(ans));
     memset(num, 0, sizeof(num));
     memset(sorted_num, 0, sizeof(sorted_num));     
}


void insert(struct Num &x) {++b[x.rank]; ++sumv[x.bulk_rank]; }
void eraser(struct Num &x) {--b[x.rank]; --sumv[x.bulk_rank]; }

int Kth(int k) {
  int cnt = 0;

  for (int i = 1; i<= uniq_c/vbsize+1; i++) {
    cnt += sumv[i];
    if (cnt >= k) {
      cnt -= sumv[i];
      for (int j = (i-1)*vbsize; ; j++) {
        cnt += b[j];
        if (cnt >= k) return uniq_n[j];
      }
    }
  }
}

int main(int argc, char *argv[]) {

    int x, y, k; 
    init();
    
    scanf("%d%d", &n, &m);
    
    qbsize = (int) sqrt(m*1.0); vbsize = (int) sqrt(n*1.0);

    // 获取输入数据并排序
    for (int i=1; i<=n; i++) {
      scanf("%d", &sorted_num[i].val);
      sorted_num[i].pos = i;
    }
    
    sort(sorted_num+1, sorted_num+n+1);

    sorted_num[0].val = 0 - sorted_num[1].val;
    for (int i=1; i<=n; i++) {
      if (sorted_num[i].val != sorted_num[i-1].val) uniq_c++;
      uniq_n[uniq_c] = sorted_num[i].val;
      sorted_num[i].rank = uniq_c; sorted_num[i].bulk_rank = uniq_c/vbsize + 1;
      num[sorted_num[i].pos] = sorted_num[i];
    }


    // 获取查询语句
    for (int i=1; i<=m; i++) {
      scanf("%d%d%d", &x, &y, &k);
      q[i].l = x; q[i].r = y;
      q[i].k = k; q[i].s = i;
      q[i].lbulk = x/qbsize; q[i].rbulk = y/qbsize;
    }
    
    sort(q+1, q+1+m);
    
    // 开始查询,并将结果存入数组中 
    for (int i = q[1].l; i<=q[1].r; i++) insert(num[i]);
    ans[q[1].s] = Kth(q[1].k);

    for (int i=2; i<=m; i++) {
      if(q[i].l<q[i-1].l) for(int j=q[i-1].l-1;j>=q[i].l;--j) insert(num[j]);
      else for(int j=q[i-1].l;j<q[i].l;++j) eraser(num[j]);
      if(q[i].r<q[i-1].r) for(int j=q[i-1].r;j>q[i].r;--j) eraser(num[j]);
      else for(int j=q[i-1].r+1;j<=q[i].r;++j) insert(num[j]);
      ans[q[i].s] = Kth(q[i].k);
    }

    for (int i = 1; i<=m; i++)
      printf("%d
", ans[i]);
    return 0;
}

主席树

  • 这题有神坑,将 input 数组放到结构体 Segment 内部,用 sort 函数排序就会各种踩内存。我只输入 10000 个数字,结果程序每次都会 coredump(sort 函数踩内存,将记录数字数字的 input pos 的值都修改改到 40000+~~~)。
  • 我的代码写的太长了,有 50 多行的版本 http://blog.csdn.net/su20145104009/article/details/51439339 (不过她的代码,在好多的地方没有换行。那份代码很难理解,我一眼没看懂就不看了~)。还有 http://www.cnblogs.com/zyf0163/p/4749042.html 的代码(只有61 行,而且比较好理解)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>

const int maxn = 1e5+10;
const int inf = 1e9+5;
const int maxq = 1e4+5;


using namespace std;
struct query
{
  int x, y, k;
}q[maxq];


int ans[maxn];
int n, m;

struct InputD{ 
    int val;
    int input_pos;
    int rank;
    int index;
    bool operator <(const InputD &a)const {
      return val < a.val;
    }
} input[maxn]; //之前将 input 放到Segment 内,结果 sort 函数各种错误!内存踩的飞起!


struct Segment {
  // 因为不需要修改
  struct _nod{
    int sums;
    int l, r;
    int leftson, rightson;
  } TreeNode[maxn * 20];  //之前只开到 maxn<<2, 结果爆内存 

  int _lazy_add_seq[maxn];  //这个数据结构只可用于 lazy_add 和 force_add 这两个函数
  
  int cnt;  // cnt = 0 用于空白树
  
  void init() {
    cnt = 0;
    memset(TreeNode, 0, sizeof(TreeNode));
    memset(input, 0, sizeof(input));
    memset(_lazy_add_seq, 0, sizeof(_lazy_add_seq));
  }

  int cnt_increment() {
    return ++cnt;
  }
  
  void build_blank_tree (int index, int l, int r) {
    int mid = (l+r) >> 1, leftson, rightson;

    if (l == r) {
      TreeNode[index].l = TreeNode[index].r = l;
      return;
    }
    
    leftson = ++cnt; rightson = ++cnt;

    TreeNode[index].l = l; TreeNode[index].r = r;
    TreeNode[index].leftson = leftson; TreeNode[index].rightson = rightson;
    
    /*
    if (l == r)
      return; 如果这里退出,会造成大量的 cnt 没被使用而导致内存溢出
    */
    
    build_blank_tree(leftson, l, mid); build_blank_tree(rightson, mid+1, r);
    return;
  }
  
  void force_add() {
    
    int tindex, mid, preindex;
    int son;
    
    for (int i=1; i<=n; i++) {
      
      tindex = input[_lazy_add_seq[i]].index;
      preindex = tindex - 1;
      
      while (true){
        TreeNode[tindex] = TreeNode[preindex];
        TreeNode[tindex].sums += 1;
        mid = (TreeNode[tindex].l + TreeNode[tindex].r) >> 1;

        if (TreeNode[tindex].l == TreeNode[tindex].r)
          break;
        
        if (mid >= input[_lazy_add_seq[i]].rank) {
          son = cnt_increment();
          TreeNode[tindex].leftson = son;
          tindex = son; preindex = TreeNode[preindex].leftson;
        }
        else {
          son = cnt_increment();
          TreeNode[tindex].rightson = son;
          tindex = son; preindex = TreeNode[preindex].rightson;
        }
      }
    }
    
    return ;
  }
  
  void lazy_add(int i, int pos) {
    _lazy_add_seq[pos] = i;
  }

  int query(int l, int r, int k) {
    int t1 = l-1, t2 = r;
    int leftdiff, rightdiff;
    while (true) {
      if (TreeNode[t2].l == TreeNode[t2].r)
        return input[TreeNode[t2].l].val;

      leftdiff = TreeNode[TreeNode[t2].leftson].sums - TreeNode[TreeNode[t1].leftson].sums;
      rightdiff = TreeNode[TreeNode[t2].rightson].sums - TreeNode[TreeNode[t1].rightson].sums;
      
      if (leftdiff >= k) {
        t1 = TreeNode[t1].leftson; t2 = TreeNode[t2].leftson;
      }
      else {
        t1 = TreeNode[t1].rightson; t2 = TreeNode[t2].rightson;
        k -= leftdiff;
      }
    }
  }
    
}T;


int main(int argc, char *argv[]) {

    int x, y, k;
    T.init();
    memset(q, 0, sizeof(q));
      
    scanf("%d%d", &n, &m);

    // get input data
    for (int i=1; i<=n; i++) {
      scanf("%d", &input[i].val);
      input[i].index = T.cnt_increment();
      input[i].input_pos = i;
    }

    // get input query
    for (int i=1; i<=m; i++) {
      scanf("%d%d%d", &x, &y, &k);
      q[i].x = x; q[i].y = y; q[i].k = k; 
    }

    // build blank tree
    T.build_blank_tree(0, 1, n);

    sort(input+1, input+n+1); //应该是踩内存了
    
    // add data
    do {
      
      for (int i=1; i<=n; i++) {
        input[i].rank = i;
        T.lazy_add(i, input[i].input_pos);
      }
      
      T.force_add();
      
    } while (0);
    // query!
    for (int i = 1; i<=m; i++)
      printf("%d
", T.query(q[i].x, q[i].y, q[i].k));
    return 0;
}

划分树

/*
 * 参考资料 url: https://www.cnblogs.com/hchlqlz-oj-mrj/p/5744308.html
 *
 */


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>

const int maxn = 1e5+10;
const int inf = 1e9+5;

using namespace std;

int ans[maxn], a[maxn+1];
int n, m, num = 0;


struct HuafengTree {

  struct data {
    int val;
    int sums;  // little than mid !
    } data[20][maxn+1];

  void init(){
    memset(data, 0, sizeof(data));
  }

  void build(int l, int r, int level) { //似乎还不能解决有重复数字的题目! 
    int p1 = 0, p2 = 0; 
    int mid = (l+r) >> 1;
    
    if (l == r)
      return ;
    
    for (int i = l; i <= r; i++) {
      if (data[level][i].val <= a[mid]) {
        data[level][i].sums = get_sum(l, i-1, level) + 1;
        data[level+1][++p1+l-1].val = data[level][i].val;
        }
      else {
        data[level][i].sums = get_sum(l, i-1, level);
        data[level+1][++p2+mid].val = data[level][i].val;
      }
    }
    
    if (p1 > 0)
      build(l, mid, level+1);
      
    if (p2 > 0)  
      build(mid+1, r, level+1);
    return;
  }

  int get_sum(int l, int t, int level) {
    if (t >= l )
      return data[level][t].sums;
    else
      return 0;
  }
             
  int look(int ceng,int sl,int sr,int l,int r,int k) 
  {
    if(sl==sr) return data[ceng][sl].val;
    int ly;  //ly 表示l 前面有多少元素进入左孩子
    if(l==sl) ly=0;  //和左端点重合时
    else ly=data[ceng][l-1].sums;
    int tolef=data[ceng][r].sums-ly;  //这一层l到r之间进入左子树的有tolef个
    if(tolef>=k)
      {
        return look(ceng+1,sl,(sl+sr)/2,sl+ly,sl+data[ceng][r].sums-1,k);
      }
    else
      {
        // l-sl 表示l前面有多少数,再减ly 表示这些数中去右子树的有多少个
        int lr = (sl+sr)/2 + 1 + (l-sl-ly);  //l-r 去右边的开头位置
        // r-l+1 表示l到r有多少数,减去去左边的,剩下是去右边的,去右边1个,下标就是lr,所以减1
        return look(ceng+1,(sl+sr)/2+1,sr,lr,lr+r-l+1-tolef-1,k-tolef);
      }
  }
  
}T;



int main(int argc, char *argv[]) {

    int x, y, k;

    T.init();
      
    scanf("%d%d", &n, &m);
    for (int i=1; i<=n; i++) {
      scanf("%d", &a[i]);
      T.data[0][i].val = a[i];
    }
    sort(a+1, a+1+n);
    
    T.build(1, n, 0);

    for (int i=1; i<=m; i++) {
      scanf("%d%d%d", &x, &y, &k);
      printf("%d
", T.look(0, 1, n, x, y, k));
    }
    /*
    for (int i = 1; i<=m; i++)
      printf("%d
", ans[i]);
    */
    return 0;
}

  • 在写查询功能时,一直没写对边界值判断。 最后只好 copy 网上资料~~谁让我在这么大的高龄学习 acm 算法~~。上面的代码不能很好地处理有重复数字的数据( https://www.cnblogs.com/hchlqlz-oj-mrj/p/5744308.html 上的代码能处理处理重复数据, 为什么我没有想到呢? 主要是自己刚接触相关算法,觉得对于某类问题。必然能被某个高级数据结构秒掉。所以不会往暴力的方向想!

树套树

  • 只要内存大,区间问题都可以用树套树。树套树真毒瘤
原文地址:https://www.cnblogs.com/tmortred/p/7992399.html