Loading...

7.29集训

上午

(睡觉ing)

主要也就是讲了数据结构

讲了树状数组,线段树的相关操作(貌似没有提到分块)

单改区查

click

秒切

区改单查

click

搞个差分数组,还是切

区改区查

click

推式子....

p的前缀和=\begin{aligned}\sum_{i=1}^p a[i] = \sum_{i=1}p\sum_{j=1}i d[j]\end{aligned}

其中d数组为差分数组

在等式最右侧的式子\(\begin{aligned}\sum_{i=1}^p\sum_{j=1}^id[j]\end{aligned}\) 中,\(d[1]\)被利用了\(p\)次,\(d[2]\)\(被利用了\)\(p-1\)次…….,因此我们可以推出

p的前缀和= \begin{aligned}\sum_{i=1}p\sum_{j=1}id[j] = \sum_{i=1}^pd[i]*(p-i+1)\end{aligned}

然后我们考虑将\(p+1\)\(-i\) 提出来

p的前缀和=\(\begin{aligned}(p+1)*\sum_{i=1}^pd[i]-\sum_{i=1}^pd[i]*i\end{aligned}\)

那么我们考虑维护两个数组的前缀和:一个数组是\(sum1[i] = d[i]\),另一个是\(sum2[i] = d[i]*i\)

查询的时候位置p的前缀和为:\((p+1)*sum1\)数组中p的前缀和-\(sum2\)数组中p的前缀和

区间[\(l,r]\)的和即:位置\(r\)的前缀和 - 位置\(l-1\)的前缀和。

修改的时候对sum1数组正常差分

\(sum2\)数组类似,对\(sum2[i]\)加上\(l*x\),给\(sum2[r+1]\)减去\((r-1)*x\)

Code
inline void jia (int x, int val) {
   for (int i = x; i <= n; i += lowbit(i))
      sum1[i] += val, sum2[i] += val*x;
}
inline void qujianjia (int l, int r, int val) {jia(l, val), jia(r+1, -val);}
inline int chaxun(int x) {
   int res(0);
   for (int i = x; i; i -= lowbit(i))
      res += (x + 1)*sum1[i] - sum2[i];
   return res;
}
inline int qujianchaxun(int l, int r) {return chaxun(r) - chaxun(l-1);}

逆序对

click

way1.我们可以用归并排序来求

如果我们想将一个序列拍成有序的,那么每次合并的时候,左右两边的序列一定是有序的

我们每次只需要考虑右边的数能与左边的数分别构成多少个逆序对

看个例子左区间\(a = \left\{5,6,7 \right\}\)右区间 \(b = \left\{1, 2, 9\right\}\)

1. 5 > 1, 发现产生了逆序对, 此时做区间没有合并的数都要比1大,所以1与左边区间共产生了三个逆序对

2. 5 > 2, 由上文可知产生了产生了三对逆序对

….

所以:

tot += (LL)mid - i + 1;

复杂度\(nlogn\)

way2.用树状数组当然是建立权值树状数组

我们考虑逆序对的定义,首先是\(i>j\),其次是\(a[i] < a[j]\)

在树状数组里面,对于权值树状数组,我们肯定考虑离散化

对于每次新进的数x,我们考虑在树状数组上在下标为x的地方加1,代表这个数存在过

而我们再来思考逆序对的定义,说白了就是在当前这个数的前面,有多少个比ta大的

求一遍\(1\) ~ \(x\)这个数的前缀和,再用一共输入的点的个数前去这部分,不就剩下了比x大的部分?

PS:\(rank[i]\)表示原序列中第\(i\)个数目前的排名(大小)是多少

#include <bits/stdc++.h>
#define int long long 
#define lowbit(x) (x&-x)
using namespace std;

const int N = 5e5+66;

int n, m, tot;
int rank[N], tree[N];

struct node {int val, id;}a[N];

inline int cmp (node x, node y) {
   return (x.val<y.val)||(x.val == y.val && x.id < y.id);
}

inline void charu (int x, int val) {
   for (int i = x; i <= n; i += lowbit(i))
      tree[i] += val;
}

inline int chaxun (int x) {
   int res = 0;
   while (x) {
      res += tree[x];
      x -= lowbit(x);
   }
   return res;
}

signed main () {
   cin >> n;
   for (int i = 1; i <= n; ++ i) {
      cin >> a[i].val;
      a[i].id = i;
   }
   sort (a + 1, a + n + 1, cmp);
   for (int i = 1; i <= n; ++ i) rank[a[i].id] = i;
   for (int i = 1; i <= n; ++ i) {
      charu(rank[i], 1);
      tot += i-chaxun(rank[i]);
   }
   cout << tot;
   return 0;
}

小火柴

click

考虑,我们需要保证的是,a序列的第k大数对应着b序列中第k大的数

所以在离散化完成后的数组内,我们假设\(a = \left\{ 4,3,1,2 \right\}\),\(b = \left\{ 1,3,2,4 \right\}\)

\(q[a[i]] = b[i]\),相当于以\(a[i]\)为关键字对\(b[i]\)进行排序

得到\(q[1] = 2, q[2] = 4, q[3] = 3, q[4] = 1\),意味着,a中的1对应着b中的2,a中的2对应着b中的4

若序列a与序列b相等,也就是说\(q[i] = i\)

我们想让\(a,b\)序列相等,就要保证q数组单调递增

所以原来问题转化为:将原来凌乱的q数组转化为一个升序数组需要的最小次数,且每一次只能交换相邻的两个数

这不是逆序对这是啥?

项链

click

\(nlogn\)离线树状数组才能做,莫队会被卡

我们首先肯定要把询问按照右端点从小到大排序,这样保证我们不会来回跳转

然后我们考虑当前这个颜色是否出现过,如果发现没有出现过,那么就令他为\(1\),如果出现过,就令前面出现的那次\(+(-1)\),这次为\(1\)

此时我们考虑两种情况,

第一种:上次出现的颜色在当前查询的区间内,那么我们新规定的颜色,完全可以取代上一次的,题目在询问我们有多少种颜色,并不在乎出现次数

第二种,上次出现的颜色不在当前查询的区间内,那更好,我们之前的修改完毕之后,我们以后肯定不会再用它,因为我们的r是从前往后扫的

每次查询的时候,我们就查询这个区间的l与r之间存在多少个1,利用树状数组可以很好的完成

#include <bits/stdc++.h>
// #define debug
#define lowbit(x) (x&-x)
using namespace std;

const int N = 2e6+66;

int n, m;
int a[N], ans[N];
int vis[N], tree[N];

struct node {int l, r, id;}q[N];

inline int cmp (node x, node y) {return x.r < y.r;}

inline void charu (int x, int val) {
   for (int i = x; i <= n; i += lowbit(i))
      tree[i] += val;
}

inline int chaxun (int x) {
   int res = 0;
   while (x) {
      res += tree[x];
      x -= lowbit(x);
   }
   return res;
}

int main () {
   scanf ("%d", &n);
   for (int i = 1; i <= n; ++ i) scanf ("%d", &a[i]);
   scanf ("%d", &m);
   for (int i = 1; i <= m; ++ i) {
      int l, r;
      scanf ("%d%d", &q[i].l, &q[i].r);
      q[i].id = i;
   }
   sort(q+1, q+m+1, cmp);
   int now = 1;
   for (int i = 1; i <= n; ++ i) {
      if (!vis[a[i]]) {
	 vis[a[i]] = i;
	 charu(i, 1);
      } else {
         charu(vis[a[i]], -1);
         charu(i, 1);
         vis[a[i]] = i;
      }
      while (q[now].r == i) {
      	 ans[q[now].id] = chaxun(q[now].r) - chaxun(q[now].l-1);
#ifdef debug
cout << "now:-->" << now << '\n';
#endif 
         ++ now;
      }
   }
#ifdef debug
printf ("\n\n\n");
#endif 
   for (int i = 1; i <= m; ++ i) cout << ans[i] << '\n';
   return 0;
}

异或和

click

题目是在求所有连续和的异或之和,即:\(\oplus \sum_\limits{i=1}^n\sum_\limits{j=0}^{i-1}sum(i)-sum(j)\)

观察数据范围,\(\sum_\limits{i=1}^{n}a[i] \leq 1e6\) --> 考虑前缀和

考虑二进制下每一位对答案的影响,所以分四种情况

设第一个数\(A\),第二个数为\(B\),所以分为\(A=1\)\(B=1\)\(A=1\)\(B=0\)的情况

\(1 - 1 = 1\)

说明第一个数的第\(k\)位为\(1\)的时候,前\(k-1\)位要比\(B\)的前\(k-1\)位小

\(1 - 0 = 1\)

说明第一个数的第\(k\)位为\(1\)的时候,前\(k-1\)位要比\(B\)的前\(k-1\)位大

\(0 - 1 = 0\)

说明第一个数的第\(k\)位为\(0\)的时候,前\(k-1\)位要比\(B\)的前\(k-1\)位小

\(0 - 1 = 1\)

说明第一个数的第\(k\)位为\(0\)的时候,前\(k-1\)位要比\(B\)的前\(k-1\)位大

然后埋坑.....

附近公园

click

先把这个序列大于等于水面的取为1,否则为0

考虑每一位置对他左边的取一个min和max,考虑min与max序列中,“1”的那些位置有几个不一样

就相当于存在了几对区间,所以

ans = differt >> 1;

然后我们考虑用min和max来围护一个什么东西,用离散化加树状数组就好

关于可持久化线段树

考虑对每一个状态都开一个线段树,为了使其优化,我们尝试当前树连之前树的边,这样会优化空间

单点修改的话,可以简单一些

对于区间修改的东西,我们得边读入边建树

而对于可持久化权值线段树,也就是主席树(jmh树)

我不会...

不会就要学啊!等我回头补上!!!

下午

讲了一下午树剖,不懂..... click

晚上

写博客 补坑

posted @ 2020-07-29 11:54  Youngore  阅读(137)  评论(0编辑  收藏  举报