树状数组维护差分序列

fenwick-01
fenwick-02

差分的性质
bib_i 为原序列 aia_i 的差分序列,则

bi={aiai1i[2,n]a1i=1b_i= \begin{cases} a_i - a_{i-1} && i \in [2, n] \\ a_1 && i = 1 \end{cases}

  • 差分序列的前缀和等于原序列

    ai=j=1ibja_i = \sum_{j = 1}^{i} b_j

Acwing242
根据差分序列的性质,我们用树状数组维护差分序列

  • 初始化树状数组,tr[i]=A[i]A[i1]\textbf{tr}[i] = A[i] - A[i-1]
    add(i,AiAi1)\textbf{add}(i, A_i-A_{i-1})
  • 区间 [l,r]+d[l, r] + d (区间内所有的数都加 dd
    add(l,d),add(r+1,d)\textbf{add}(l, d), \textbf{add}(r+1, -d)
  • 询问修改后下标为 xx 的值,执行
    sum(x)\textbf{sum}(x)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const int maxn = 100000 + 10;
ll a[maxn], tr[maxn];
int n, m;

void add(int x, int d) {
for (int i = x; i <= n; i += lowbit(i)) tr[i] += d;
}

ll sum(int x) {
ll ans = 0;
for (int i = x; i; i -= lowbit(i)) ans += tr[i];
return ans;
}

int main() {
freopen("input.txt", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);

// prework
for (int i = 1; i <= n; i++) add(i, a[i]-a[i-1]);

// solve
while (m--) {
char cmd[2];
scanf("%s", cmd);
if (cmd[0] == 'Q') {
int x;
scanf("%d", &x);
printf("%lld\n", sum(x));
}
else {
int l, r, d;
scanf("%d%d%d", &l, &r, &d);
add(l, d), add(r+1, -d);
}
}
}

差分序列维护原序列部分和

axa_x 的前缀和 S=i=1xaiS = \sum_{i=1}^{x} a_i
如果用差分数组来维护

S=i=1xj=1ibj=i=1x(xi+1)biS = \sum_{i=1}^{x} \sum_{j=1}^{i} b_j = \sum_{i=1}^{x} (x-i+1)\cdot b_i

也就是说

S(x)=(x+1)i=1xbii=1xibiS(x) = (x+1) \sum_{i=1}^{x} b_i - \sum_{i=1}^{x} i \cdot b_i

  • 用两个树状数组 tr1,tr2\textbf{tr1}, \textbf{tr2} 分别维护序列
    {bi},{ibi}\{b_i\}, \{ib_i\}
  • [l,r]+d[l, r] + d,转为单点增加
    add(tr1,l,d)\textbf{add}(\textbf{tr1}, l, d)add(tr1,r+1,d)\textbf{add}(\textbf{tr1}, r+1, -d)
    add(tr2,l,ld)\textbf{add}(\textbf{tr2}, l, l\cdot d)add(tr2,r+1,(r+1)d)\textbf{add}(\textbf{tr2}, r+1, -(r+1)\cdot d)
  • [l,r][l, r] 部分和,根据表达式 S(r)S(l1)S(r)-S(l-1)
    S(r)=(r+1)sum(tr1,r)sum(tr2,r)S(r) = (r+1) \cdot \textbf{sum}(\textbf{tr1}, r) - \textbf{sum}(\textbf{tr2}, r)

Acwing243

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const int maxn = 100000 + 10;
ll tr1[maxn], tr2[maxn], a[maxn];
int n, m;

void add(ll tr[], int x, ll d) {
for (int i = x; i <= n; i += lowbit(i)) tr[i] += d;
}

ll sum(const ll tr[], int x) {
ll res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}

ll S(int x) {
return (x+1) * sum(tr1, x) - sum(tr2, x);
}

int main() {
freopen("input.txt", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);

// prework
for (int i = 1; i <= n; i++) {
add(tr1, i, a[i]-a[i-1]);
add(tr2, i, i * (a[i]-a[i-1]));
}

// solve
while (m--) {
char cmd[2];
scanf("%s", cmd);
if (cmd[0] == 'Q') {
int l, r;
scanf("%d%d", &l, &r);
ll res = S(r) - S(l-1);
printf("%lld\n", res);
}
else {
int l, r, d;
scanf("%d%d%d", &l, &r, &d);
add(tr1, l, d), add(tr1, r+1, -d);
add(tr2, l, (ll)l*d), add(tr2, r+1, -(ll)(r+1)*d);
}
}
}

线段树维护差分序列

区间最大公约数问题

gcd(x1,x2)=gcd(x1,x2x1)gcd(xl,xl+1,xl+2xn)=gcd(xl,xl+1xl,xl+2xl+1)\begin{gathered} \textbf{gcd}(x_1, x_2) = \textbf{gcd}(x_1, x_2 - x_1) \\ \textbf{gcd}(x_l, x_{l+1}, x_{l+2} \cdots x_n) = \textbf{gcd}(x_l, x_{l+1}-x_l, x_{l+2}-x_{l+1} \cdots ) \end{gathered}

  • 算法设计

    gcd()线段树维护[xlxr]+d树状数组维护\begin{gathered} \textbf{gcd}(\cdots) \quad \text{线段树维护} \\ [x_l\cdots x_r] + d \quad \text{树状数组维护} \end{gathered}

  • 初始化,令 bi=aiai1b_i = a_i - a_{i-1}
    • 对差分序列 b[1n]b[1\cdots n] 建立线段树维护 gcd\textbf{gcd}
    • 同时初始化树状数组 add(tr[],i,bi)\textbf{add}(tr[\cdots], i, b_i)
  • 对于命令 C l r dC \ l \ r \ d 用线段树和树状数组同步维护
    • 树状数组 add(l,d), add(r+1,d)\textbf{add}(l, d), \ \textbf{add}(r+1, -d)
    • 线段树 change(1,l,d), change(1,r+1,d)\textbf{change}(1, l, d), \ \textbf{change}(1, r+1, -d)
  • 对于命令 Q l rQ \ l \ r
    al=sum(l),resquery(1,l+1,r)a_l = \textbf{sum}(l), res \leftarrow \textbf{query}(1, l+1, r)
    gcd(al,res)\textbf{gcd}(a_l, res)
  • 坑点,执行 [l,r]+d[l, r] + d 的时候,边界条件是 bn+1db_{n+1} - d
    建线段树的时候,是根据区间 [1,n+1][1, n+1] 来建立线段树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
const int maxn = 500000 + 10;
ll a[maxn], b[maxn];
int n, m;

ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}

ll tr[maxn];
void add(int x, ll d) {
for (int i = x; i <= n; i += lowbit(i)) tr[i] += d;
}
ll sum(int x) {
ll res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}

class A {
public:
int l, r;
ll val;
} t[maxn<<2];

void build(int p, int l, int r) {
t[p].l = l; t[p].r = r;
if (l == r) {
t[p].val = b[l];
return;
}
int mid = (l+r) >> 1;
if (l <= mid) build(p<<1, l, mid);
if (r > mid) build(p<<1 | 1, mid + 1, r);

t[p].val = gcd(t[p<<1].val, t[p<<1 | 1].val);
}


void change(int p, int x, ll d) {
if (x == t[p].l && t[p].r == x) {
t[p].val += d;
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid) change(p<<1, x, d);
if (x > mid) change(p<<1 | 1, x, d);
t[p].val = gcd(t[p<<1].val, t[p<<1 | 1].val);
}

ll query(int p, const int l, const int r) {
if (l > r) return 0;
if (l <= t[p].l && t[p].r <= r) return t[p].val;

int mid = (t[p].l + t[p].r) >> 1;
ll res = 0;
if (l <= mid) res = gcd(res, query(p<<1, l, r));
if (r > mid) res = gcd(res, query(p<<1 | 1, l, r));
return res;
}

void prework() {
for (int i = 1; i <= n; i++) {
add(i, b[i]);
}
build(1, 1, n+1);
}

int main() {
freopen("input.txt", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for (int i = 1; i <= n; i++) b[i] = a[i] - a[i-1];

// use tr[...] and seg-tree
prework();

// solve command
while (m--) {
char cmd[2];
scanf("%s", cmd);
if (cmd[0] == 'Q') {
int l, r;
scanf("%d%d", &l, &r);
ll res = query(1, l+1, r);
ll al = sum(l);
res = gcd(al, res);
printf("%lld\n", abs(res));
}
else {
int l, r;
ll d;
scanf("%d%d%lld", &l, &r, &d);
change(1, l, d); change(1, r+1, -d);
add(l, d); add(r+1, -d);
}
}
}

差分 dp

dp 中的背包模型

背包问题中容易混淆的是 0-1背包和完全背包
0-1背包和完全背包,dp阶段的更新方式分别如左右两图所示
dp1

  • 0-1 背包\textbf{0-1} \ \textbf{背包}

    • f(i,j)=max{f(i1,j)不选第 i 个物品f(i1,jVi)+WiVij<n选择第 i 个物品 f(i, j) = \max \begin{cases} f(i-1, j) && \text{不选第} \ i \ \text{个物品} \\ f(i-1, j-V_i) + W_i \quad V_i \leqslant j < n && \text{选择第} \ i \ \text{个物品} \end{cases}

    • ii 阶段只与第 i1i-1 阶段有关,所以可以滚动更新
      {i1}{i}\{i-1\} \to \{i\}
    • for j=n downto Vi\textbf{for} \ \forall j = n \ \textbf{downto} \ V_i
  • 完全背包\textbf{完全背包}

    • 状态转移

      f(i,j)=max{f(i1,j)第 i 种物品一个都不选f(i,jVi)+WiVij<n继续选第 i 种物品 f(i, j) = \max \begin{cases} f(i-1, j) && \text{第} \ i \ \text{种物品一个都不选} \\ f(i, j-V_i) + W_i \quad V_i \leqslant j < n && \text{继续选第} \ i \ \text{种物品} \end{cases}

    • 同样可以滚动更新,此时阶段的变化是
      {i}{i}\{i\} \to \{i\},用当前的阶段更新当前
    • for j=Vi to n\textbf{for} \ \forall j = V_i \ \textbf{to} \ n

差分算法解决 dp 问题

Problem Scores
问题简述
序列中有 nn 个元素,满足 A1A2AnA_1 \leqslant A_2 \leqslant \cdots \leqslant A_n
从中选取任意 kk 个元素,他们的和为 ka[]\sum_{k} a[\cdots]
从中选取任意 k+1k+1 个元素,他们的和为 k+1a[]\sum_{k+1} a[\cdots]
必须保证 ka[]<k+1a[]\sum_{k} a[\cdots] < \sum_{k+1} a[\cdots],求满足条件的方案数

注意到此时 A1A2AnA_1 \leqslant A_2 \leqslant \cdots \leqslant A_n
所以原问题可以转换成为

i=1k+1aii=nk+1nai\sum_{i=1}^{k+1} a_i \geqslant \sum_{i=n-k+1}^{n} a_i

先证明一个性质,如果 nk+1=k+1n-k+1 = k+1,即 k=n2k = \lfloor \frac{n}{2}\rfloor 满足
则对所有的 1kn11 \leqslant k \leqslant n-1 均满足
证明如下
agc-041D
问题转换为满足

i=1k+1aii=nk+1nai(k=n2)\sum_{i=1}^{k+1} a_i \geqslant \sum_{i=n-k+1}^{n} a_i \quad (k = \left \lfloor \frac{n}{2} \right \rfloor)

  • 构造差分序列 xi=aiai1x_i= a_i - a_{i-1}
    x0=1x_0 = 1,这是因为得分不能为 00,可能所有题目得分都是一样的
    此时 i=1nxi=0\sum_{i=1}^{n} x_i = 0 不满足题意
    所以令 x0=1x_0 = 1,则此时 an1a_n \geqslant 1 成立

    an=1+(i=1nxi)a_n = 1 + \left(\sum_{i = 1}^{n}x_i \right)

  • n2=n12\left \lfloor \frac{n}{2} \right \rfloor = \frac{n-1}{2}

    i=1n+12aii=n+12+1nai 2(i=1n+12ai)i=1nai2i=1n+12(n+12i+1)xii=1n(ni+1)xi\begin{gathered} \sum_{i=1}^{\frac{n+1}{2}} a_i \geqslant \sum_{i = \frac{n+1}{2} + 1}^{n} a_i \\ \Rightarrow \ 2 \left(\sum_{i=1}^{\frac{n+1}{2}} a_i \right)\geqslant \sum_{i = 1}^{n} a_i \\ \Rightarrow 2 \sum_{i=1}^{\frac{n+1}{2}} \left( \frac{n+1}{2} - i + 1 \right) x_i \geqslant \sum_{i=1}^{n}(n-i+1)x_i \end{gathered}

    移项,上式可以写成
    i=1nCixi0\sum_{i = 1}^{n} C_i\cdot x_i \leqslant 0

    Ci={ni+1i>n+12i2in+12C_i = \begin{cases} n-i+1 && i > \frac{n+1}{2} \\ i-2 && i \leqslant \frac{n+1}{2} \end{cases}

  • n2=n2\left \lfloor \frac{n}{2} \right \rfloor = \frac{n}{2}
    同理可以求出

    2(i=1n2ai)(i=1nai)an2+12(i=1n2(n2i+1)xi)i=1n(ni+1)xii=1n2+1xi\begin{gathered} 2 \left( \sum_{i=1}^{\frac{n}{2}}a_i \right) \geqslant \left( \sum_{i=1}^{n} a_i \right) - a_{\frac{n}{2} + 1} \\ 2\left( \sum_{i=1}^{\frac{n}{2}} \left(\frac{n}{2} - i+1 \right) x_i \right) \geqslant \sum_{i=1}^{n}\left( n-i+1 \right)x_i - \sum_{i=1}^{\frac{n}{2}+1} x_i \end{gathered}

    写成如下形式
    i=1nCixi0\sum_{i=1}^{n}C_i \cdot x_i \leqslant 0

    Ci={nii=n2+1ni+1i>n2+1i2in2C_i = \begin{cases} n-i && i = \frac{n}{2} + 1 \\ n-i+1 && i > \frac{n}{2} + 1 \\ i-2 && i \leqslant \frac{n}{2} \end{cases}

    注意到 i=n2+1i=\frac{n}{2} + 1 时候,此时 i2=nii-2=n-i
    综上所述

    Ci={ni+1i>n2+1i2in2+1C_i = \begin{cases} n-i+1 && i > \left\lfloor \frac{n}{2} \right\rfloor + 1 \\ i-2 && i \leqslant \left\lfloor \frac{n}{2} \right\rfloor + 1 \end{cases}

  • 问题转换为满足以下条件

    {i=1nCixi01+(i=1nxi)n\begin{cases} \sum_{i =1}^{n} C_i x_i \leqslant 0 \\ 1 + \left( \sum_{i=1}^{n}x_i \right) \leqslant n \end{cases}

    合法的 xix_i 取值方案数(注意此时 xix_i 未知),其中

    Ci={ni+1i>n2+1i2in2+1C_i = \begin{cases} n-i+1 && i > \left\lfloor \frac{n}{2} \right\rfloor + 1 \\ i-2 && i \leqslant \left\lfloor \frac{n}{2} \right\rfloor + 1 \end{cases}

    这是一个线性约束条件,对 ii 归纳,只要 x1x_1 满足约束
    可以递推出其余的 x2xnx_2 \cdots x_n 方案

    化简得到

    {x1(n1)(i=2nxi)x1i=2nCixi\begin{cases} x_1 \leqslant (n-1) - \left( \sum_{i=2}^{n} x_i \right) \\ x_1 \geqslant \sum_{i=2}^{n} C_ix_i \end{cases}

    将不等式看作关于 x1x_1 取值区间的两个端点,x1x_1 能取的值一共有

    cnt=max(0,ni=2n(Ci+1)xi)\textbf{cnt} = \max\left( 0, n - \sum_{i=2}^{n}(C_i + 1)x_i \right)

  • 这个问题可以用 O(n2)O(n^2) 的 dp 解决
    假设满足 i=2n(Ci+1)xi=P\sum_{i=2}^{n}\left( C_i+1 \right) x_i = P,此时对于{xi}\{x_i\} 序列,合法的方案数有 f(P)f(P)
    (注意此时 xix_i 未知)
    则最后的答案是
    (nP)f(P),P=Vixi(Vi=Ci+1)(n-P) \cdot f(P), \quad P = \sum V_ix_i \quad (V_i = C_i+1)

    • f(P)f(P) 可以通过完全背包的刷表法解决
      f(P)=i=2n(Ci+1)xif(P) = \sum_{i=2}^{n} (C_i+1)x_i,此时背包的物品种类 i[2,n]i \in [2, n]
      每一种物品的个数 xix_i 未知,但每一种物品可以重复选
      ii 种物品的体积为 Vi=Ci+1V_i = C_i+1
      Vixi=P\sum V_ix_i = P 其实相当于背包的容积为 PPP[0n]P \in [0 \cdots n],对于第 ii 阶段递推式
      P[Vin]P \in [V_i\cdots n]
    • f(i,P)f(i,P)+f(i,PVi),P[Vin]f(i,P) \leftarrow f(i,P) + f(i, P-V_i), \quad P\in [V_i \to n]
      f(0)=1f(0) = 1,此时我们有 i, xi=0\forall i, \ x_i = 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const int maxn = 5000 + 10;
int n, m, C[maxn];
ll f[maxn];

void prework() {
for (int i = 1; i <= n; i++) {
int mid = n / 2;
if (i > mid + 1) C[i] = n-i+2;
else C[i] = i-1;
}
}

void dp() {
memset(f, 0, sizeof f);
f[0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = C[i]; j <= n; j++) {
f[j] = (f[j] + f[j-C[i]]) % m;
}
}

ll ans = 0;
for (int i = 0; i <= n; i++) {
ans = (ans + 1ll * (n-i) * f[i]) % m;

}
printf("%lld\n", ans);
}

int main() {
freopen("input.txt", "r", stdin);
cin >> n >> m;

// prework
prework();
// dp
dp();
}