树状数组维护差分序列


差分的性质
bi 为原序列 ai 的差分序列,则
bi={ai−ai−1a1i∈[2,n]i=1
Acwing242
根据差分序列的性质,我们用树状数组维护差分序列
- 初始化树状数组,tr[i]=A[i]−A[i−1]
add(i,Ai−Ai−1) 
- 区间 [l,r]+d (区间内所有的数都加 d)
add(l,d),add(r+1,−d) 
- 询问修改后下标为 x 的值,执行
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);         }     } }
   | 
 
差分序列维护原序列部分和
ax 的前缀和 S=∑i=1xai
如果用差分数组来维护
S=i=1∑xj=1∑ibj=i=1∑x(x−i+1)⋅bi
也就是说
S(x)=(x+1)i=1∑xbi−i=1∑xi⋅bi
- 用两个树状数组 tr1,tr2 分别维护序列
{bi},{ibi} 
- [l,r]+d,转为单点增加
add(tr1,l,d), add(tr1,r+1,−d)
add(tr2,l,l⋅d), add(tr2,r+1,−(r+1)⋅d) 
- [l,r] 部分和,根据表达式 S(r)−S(l−1)
S(r)=(r+1)⋅sum(tr1,r)−sum(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,x2−x1)gcd(xl,xl+1,xl+2⋯xn)=gcd(xl,xl+1−xl,xl+2−xl+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阶段的更新方式分别如左右两图所示

差分算法解决 dp 问题
Problem Scores
问题简述
序列中有 n 个元素,满足 A1⩽A2⩽⋯⩽An
从中选取任意 k 个元素,他们的和为 ∑ka[⋯]
从中选取任意 k+1 个元素,他们的和为 ∑k+1a[⋯]
必须保证 ∑ka[⋯]<∑k+1a[⋯],求满足条件的方案数
注意到此时 A1⩽A2⩽⋯⩽An
所以原问题可以转换成为
i=1∑k+1ai⩾i=n−k+1∑nai
先证明一个性质,如果 n−k+1=k+1,即 k=⌊2n⌋ 满足
则对所有的 1⩽k⩽n−1 均满足
证明如下

问题转换为满足
i=1∑k+1ai⩾i=n−k+1∑nai(k=⌊2n⌋)
- 
构造差分序列 xi=ai−ai−1
令 x0=1,这是因为得分不能为 0,可能所有题目得分都是一样的
此时 ∑i=1nxi=0 不满足题意
所以令 x0=1,则此时 an⩾1 成立
an=1+(i=1∑nxi)
 
- 
⌊2n⌋=2n−1
i=1∑2n+1ai⩾i=2n+1+1∑nai⇒ 2⎝⎛i=1∑2n+1ai⎠⎞⩾i=1∑nai⇒2i=1∑2n+1(2n+1−i+1)xi⩾i=1∑n(n−i+1)xi
移项,上式可以写成
∑i=1nCi⋅xi⩽0
Ci={n−i+1i−2i>2n+1i⩽2n+1
 
- 
⌊2n⌋=2n
同理可以求出
2⎝⎛i=1∑2nai⎠⎞⩾(i=1∑nai)−a2n+12⎝⎛i=1∑2n(2n−i+1)xi⎠⎞⩾i=1∑n(n−i+1)xi−i=1∑2n+1xi
写成如下形式
∑i=1nCi⋅xi⩽0
Ci=⎩⎪⎪⎨⎪⎪⎧n−in−i+1i−2i=2n+1i>2n+1i⩽2n
注意到 i=2n+1 时候,此时 i−2=n−i
综上所述
Ci={n−i+1i−2i>⌊2n⌋+1i⩽⌊2n⌋+1
 
- 
问题转换为满足以下条件
{∑i=1nCixi⩽01+(∑i=1nxi)⩽n
合法的 xi 取值方案数(注意此时 xi 未知),其中
Ci={n−i+1i−2i>⌊2n⌋+1i⩽⌊2n⌋+1
这是一个线性约束条件,对 i 归纳,只要 x1 满足约束
可以递推出其余的 x2⋯xn 方案
化简得到
{x1⩽(n−1)−(∑i=2nxi)x1⩾∑i=2nCixi
将不等式看作关于 x1 取值区间的两个端点,x1 能取的值一共有
cnt=max(0,n−i=2∑n(Ci+1)xi)
 
- 
这个问题可以用 O(n2) 的 dp 解决
假设满足 ∑i=2n(Ci+1)xi=P,此时对于{xi} 序列,合法的方案数有 f(P) 个
(注意此时 xi 未知)
则最后的答案是
(n−P)⋅f(P),P=∑Vixi(Vi=Ci+1)
- f(P) 可以通过完全背包的刷表法解决
f(P)=∑i=2n(Ci+1)xi,此时背包的物品种类 i∈[2,n]
每一种物品的个数 xi 未知,但每一种物品可以重复选
第 i 种物品的体积为 Vi=Ci+1
∑Vixi=P 其实相当于背包的容积为 P,P∈[0⋯n],对于第 i 阶段递推式
P∈[Vi⋯n] 
- f(i,P)←f(i,P)+f(i,P−Vi),P∈[Vi→n]
f(0)=1,此时我们有 ∀i, xi=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(); }
   |